<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.billmongan.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.billmongan.com/" rel="alternate" type="text/html" /><updated>2026-04-13T14:44:25+00:00</updated><id>https://www.billmongan.com/feed.xml</id><title type="html">William M. Mongan, Ph.D.</title><subtitle>Homepage of Dr. Bill Mongan</subtitle><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><entry><title type="html">Setting Up AREDN on a Mikrotik hAp to use 44net</title><link href="https://www.billmongan.com/posts/2025/06/aredn44/" rel="alternate" type="text/html" title="Setting Up AREDN on a Mikrotik hAp to use 44net" /><published>2025-06-30T00:00:00+00:00</published><updated>2025-06-30T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2025/06/aredn44</id><content type="html" xml:base="https://www.billmongan.com/posts/2025/06/aredn44/"><![CDATA[<p>This guide will walk you through setting up a Mikrotik hAp device (I used a hAp ac2) to use 44net addresses, bridging AREDN and 44net services between the two networks.  I set up the hAp to broadcast a WiFi hotspot SSID that, when connected to a client, enables access to both 44net and to AREDN resources simultaneously.  I use <a href="https://connect.44net.cloud">44net Connect</a> (formerly 44net.cloud) to route a network allocation they assigned to me through a Wireguard tunnel that they also assigned.  The tunnel can be configured through their portal to route to the network.  It is likely also possible to do this by decapsulating the ipencap packets from the raspberry pi directly, and using a traditional 44net subnet allocation, but this setup enables me to take the hAp setup to mobile deployments, without worring about the NAT configuration or my ability to forward ipencap traffic at my destination.</p>

<p>I took the following steps for this setup:</p>

<ol>
  <li>Obtain and use a 44net IP allocation via 44net Connect</li>
  <li>Set up a Raspberry Pi Zero 2 W as a WireGuard gateway router, including NAT and <code class="language-plaintext highlighter-rouge">iptables</code> firewall with <code class="language-plaintext highlighter-rouge">fail2ban</code></li>
  <li>Connect AREDN node (e.g., hAp2) via the 44Net tunnel</li>
  <li>Write a script to configure the routing table on the AREDN node, in case it is not peristed on router reboot</li>
</ol>

<h2 id="set-up-a-44net-allocation-and-tunnel-from-44net-connect">Set up a 44net Allocation and Tunnel from 44net Connect</h2>

<p>This step could be replaced with your existing 44net allocation, but, as I mentioned, I chose to work through the 44net Connect tunnel service and network allocation generously offered by their service.  Although I have traditional 44net subnets allocated for fixed use, I felt that the tunneled approach would give me some versatility in the deployment behind a variety of WAN connections.  Because I have an interest in computing and networking education, the ability to pack up and redeploy this setup in a mobile environment was advantageous for me.</p>

<p>Go to <a href="https://connect.44net.cloud">https://44net Connect</a> and create an account</p>

<p>Request a routed subnet (e.g., <code class="language-plaintext highlighter-rouge">44.x.y.z/29</code>) and a WireGuard tunnel</p>

<p>You’ll receive a:</p>

<ul>
  <li>Tunnel endpoint (e.g., <code class="language-plaintext highlighter-rouge">a.b.c.d:44000</code>)</li>
  <li>Allocated IP (e.g., <code class="language-plaintext highlighter-rouge">44.i.j.k/32</code>)</li>
  <li>Route for your subnet via that tunnel by editing the tunnel allocation and specifying static routing to the subnet</li>
</ul>

<h2 id="configure-a-raspberry-pi-as-a-gateway-to-44net-connect-for-the-aredn-router">Configure a Raspberry Pi as a Gateway to 44net Connect for the AREDN Router</h2>

<p>Although the router should generally support it, the AREDN firmware does not allow making a non-AREDN Wireguard connection such as the one I am using to connect to 44net Connect in order to route this network allocation (at least as far as I’m aware at the time of this writing!).  Instead, I’ve set up a Raspberry Pi Zero 2W to serve this purpose.  I’ll connect the pi to the AREDN router via its WiFI LAN Hotspot connection, and give the pi an IP address on the 44net subnet allocation alongside the router, and will set it up for IPv4 forwarding.</p>

<h3 id="configure-the-wireguard-tunnel">Configure the Wireguard Tunnel</h3>

<h4 id="install-wireguard">Install WireGuard</h4>

<p>Here, we create the Wireguard tunnel connection to 44net Connect from the Raspberry Pi, and configure the IP routes on the pi to send 44net traffic out via this Wireguard interface.  In addition, we route our local 44net subnet out via our LAN connection, so that it goes directly to the AREDN router.  In my case, I’m connected to the AREDN router via a wifi Part 15 connection, so I use <code class="language-plaintext highlighter-rouge">wlan0</code> for this interface.  If you plug the pi into the AREDN router via Ethernet, you might use <code class="language-plaintext highlighter-rouge">eth0</code> here instead.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt update
sudo apt install wireguard netfilter-persistent iptables-persistent resolvconf sshpass
</code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">/etc/wireguard/wg0.conf</code>, using your 44net subnet allocation for <code class="language-plaintext highlighter-rouge">44.x.y.z/29</code>, your 44net Connect Wireguard tunnel keys, and substituting <code class="language-plaintext highlighter-rouge">wlan0</code> for the LAN interface you’re using from the pi to the AREDN node, as well as <code class="language-plaintext highlighter-rouge">44.a.b.c</code> for your wireguard tunnel client address and <code class="language-plaintext highlighter-rouge">a.b.c.d:44000</code> for your tunnel endpoint:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
PrivateKey = &lt;local private key&gt;
Address = 44.a.b.c
DNS = 1.1.1.1,1.0.0.1

PostUp = ip route add 44.x.y.z/29 dev wlan0
PostDown = ip route del 44.x.y.z/29 dev wlan0

[Peer]
PublicKey = &lt;peer public key&gt;
PresharedKey = &lt;preshared key&gt;
Endpoint = a.b.c.d:44000
AllowedIPs = 44.0.0.0/9, 44.128.0.0/10
PersistentKeepalive = 20
</code></pre></div></div>

<h4 id="enable-and-start-wireguard">Enable and Start WireGuard</h4>

<p>Create <code class="language-plaintext highlighter-rouge">/etc/systemd/system/wg-44net-client.service</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=WireGuard Client to 44net (wg0)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/wg-quick up wg0
ExecStop=/usr/bin/wg-quick down wg0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
</code></pre></div></div>

<h4 id="enable-the-service-for-automatic-startup">Enable the Service for Automatic Startup</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl enable wg-44net-client
sudo systemctl start wg-44net-client
</code></pre></div></div>

<h3 id="set-static-ip-enable-ip-forwarding-and-disable-rp-filtering">Set Static IP, Enable IP Forwarding, and Disable RP Filtering</h3>

<p>Set the AREDN router (hap2) to give a static IP to the pi.  For example, if your 44net subnet is <code class="language-plaintext highlighter-rouge">44.a.b.32/29</code>, then the AREDN router might take <code class="language-plaintext highlighter-rouge">44.a.b.33/29</code> and the pi could be assigned <code class="language-plaintext highlighter-rouge">44.a.b.34/29</code> as a DHCP reservation from the AREDN router.</p>

<p>To enable IPv4 forwarding, edit <code class="language-plaintext highlighter-rouge">/etc/sysctl.conf</code> and add or uncomment the following line:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net.ipv4.ip_forward=1
</code></pre></div></div>

<p>Apply the changes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo sysctl -p
</code></pre></div></div>

<p>If you find that <code class="language-plaintext highlighter-rouge">ip_forward</code> is still <code class="language-plaintext highlighter-rouge">1</code> when you execute <code class="language-plaintext highlighter-rouge">cat /proc/sys/net/ipv4/ip_forward</code> on the AREDN node or on the Pi, update them by running: <code class="language-plaintext highlighter-rouge">echo 1 &gt; /proc/sys/net/ipv4/ip_forward</code>.</p>

<p>On both the AREDN router, and on the Pi, ensure that IPv4 forwarding is enabled, and that RP filtering is disabled, by running:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /proc/sys/net/ipv4/conf
find . -iname 'rp_filter' -exec cat {} \;
</code></pre></div></div>

<p>If you find any that are set to <code class="language-plaintext highlighter-rouge">1</code> or <code class="language-plaintext highlighter-rouge">2</code>, update them via: <code class="language-plaintext highlighter-rouge">echo 0 &gt; /proc/sys/net/ipv4/conf/&lt;name&gt;/rp_filter</code>.</p>

<h3 id="set-up-nat-and-firewall-on-the-pi">Set up NAT and Firewall on the Pi</h3>

<p>Note that this is a minimal configuration of the firewall to enable the functionality, and should be hardened to filter traffic from the Internet.  This firewall is configured to allow all traffic from AREDN (<code class="language-plaintext highlighter-rouge">10.0.0.0/8</code>) and 44net (<code class="language-plaintext highlighter-rouge">44.0.0.0/9</code> and <code class="language-plaintext highlighter-rouge">44.128.0.0/10</code>), as well as bidirectional AREDN Wireguard tunnels on UDP port <code class="language-plaintext highlighter-rouge">5525</code>.  We also allow ICMP ping packets, and any established connection packets.  Finally, we masquerade traffic on the 44net Connect tunnel Wireguard interface <code class="language-plaintext highlighter-rouge">wg0</code>.  I do not explicitly allow TCP port 22 SSH traffic from the Internet, but rather allow ssh connections via the AREDN and 44net subnets (this is also overly permissive, but is presented this way to demonstrate the functionality).</p>

<p>Edit <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code>, substituting your 44net subnet allocation for <code class="language-plaintext highlighter-rouge">44.x.y.z/29</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -s 44.x.y.z/29 -o wg0 -j MASQUERADE
COMMIT

*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]

# Allow loopback
-A INPUT -i lo -j ACCEPT

# Allow established and related
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow ALL traffic from 44.0.0.0/9
-A INPUT -s 44.0.0.0/9 -j ACCEPT

# Allow ALL traffic from 44.128.0.0/10
-A INPUT -s 44.128.0.0/10 -j ACCEPT

# Allow ALL traffic from 10.0.0.0/8
-A INPUT -s 10.0.0.0/8 -j ACCEPT

# Allow AREDN tunnels
-A INPUT -p udp --dport 5525 -j ACCEPT
-A INPUT -p udp --sport 5525 -j ACCEPT

# Optional: Allow ICMP for ping and diagnostics
-A INPUT -p icmp -j ACCEPT

# Forwarding rules: Send AREDN Wireguard traffic from 44net Connect to the AREDN router
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
COMMIT
</code></pre></div></div>

<p>Load and save these rules:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo iptables-restore &lt; /etc/iptables/rules.v4
sudo netfilter-persistent save
</code></pre></div></div>

<h3 id="implement-fail2ban-on-ssh">Implement fail2ban on SSH</h3>

<p>Install <code class="language-plaintext highlighter-rouge">fail2ban</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install fail2ban
</code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.local</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[sshd]
enabled = true
backend = systemd
logpath = journal
port = ssh
maxretry = 5
findtime = 600
bantime = 86400
</code></pre></div></div>

<p>Start and Enable <code class="language-plaintext highlighter-rouge">fail2ban</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl enable fail2ban
sudo systemctl start fail2ban
</code></pre></div></div>

<h2 id="configure-the-aredn-node-router">Configure the AREDN Node Router</h2>

<p>Configure the node as with a 44net allocation for its local LAN, and specify its IP address (for example, <code class="language-plaintext highlighter-rouge">44.x.y.33/29</code> for an allocation of <code class="language-plaintext highlighter-rouge">44.x.y.32/29</code>).  I used <code class="language-plaintext highlighter-rouge">wan0</code> as a LAN hotspot so that my devices could connect to this router wirelessly over 2.4 ghz, and <code class="language-plaintext highlighter-rouge">wlan1</code> as a WAN client to my home wireless internet connection.</p>

<p>I also created a tunnel server on my home AREDN router node, to which this new 44net AREDN node connects.  Because my home AREDN node is exposed with a 44net IP address on a different subnet, the two are able to reach each other via the 44net Connect Wireguard tunnel via the Raspberry pi.  So, I used the 44net address of my home AREDN router as the public IP from which this new node connects.</p>

<p>Finally, I allow WAN access to my LAN nodes (and provide a default route) through the network settings on the main page of the GUI.</p>

<h2 id="configure-the-routing-table-on-the-aredn-router">Configure the Routing Table on the AREDN Router</h2>

<p>Now, we will configure the AREDN router to route 44net traffic out via the Raspberry Pi, since the pi has a Wireguard connection to the 44net Connect tunnel.</p>

<p>Custom routes on the AREDN router are overwritten on reboot (as far as I can tell).  In addition, the home directory is <code class="language-plaintext highlighter-rouge">/tmp</code>, so I was unable to use ssh key login to script the creation of these routes via ssh when the Raspberry pi boots up.</p>

<p>But, for good measure, I created the following startup script on the AREDN node after logging in via <code class="language-plaintext highlighter-rouge">ssh root@localnode.local.mesh -p 2222</code> and creating a file <code class="language-plaintext highlighter-rouge">/etc/init.d/customroutes</code> (substitute <code class="language-plaintext highlighter-rouge">44.x.y.z/29</code> for your 44net subnet allocation, and <code class="language-plaintext highlighter-rouge">44.x.y.A</code> for the reserved DHCP address of your Raspberry Pi that you assigned earlier from your AREDN node out of your 44net subnet allocation, in addition to a route for your tunnel endpoint <code class="language-plaintext highlighter-rouge">44.a.b.c</code> in case it is within one of the routing blocks like <code class="language-plaintext highlighter-rouge">44.0.0.0/9</code> (so that it doesn’t go out over the same interface, but instead over your WAN default route gateway (which we will detect with the <code class="language-plaintext highlighter-rouge">get_wlan1_gw</code> function below) and <code class="language-plaintext highlighter-rouge">wlan1</code> (use your WAN interface for <code class="language-plaintext highlighter-rouge">wlan1</code> everywhere here):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh /etc/rc.common

START=95
STOP=10

get_wlan1_gw() {
    # Output gateway for default route on wlan1, or empty string if not found
    set -- $(ip -4 route show default dev wlan1 2&gt;/dev/null)
    [ "$1" = "default" ] &amp;&amp; [ "$2" = "via" ] &amp;&amp; echo "$3"
}

start() {
    logger -t customroutes "Adding custom IP routes"
    sleep 5
    ip rule add to 44.0.0.0/9 lookup main priority 5 
    ip rule add to 44.128.0.0/10 lookup main priority 6 
    ip route add 44.0.0.0/9 via 44.x.y.A
    ip route add 44.128.0.0/10 via 44.x.y.A
    ip route add 44.x.y.z/29 dev br-lan
    
    gw="$(get_wlan1_gw)"
    ip route add 44.a.b.c via "$gw" dev wlan1 
    
    logger -t customroutes "Custom IP routes added"
}

stop() {
    logger -t customroutes "Removing custom IP routes"
    ip rule del to 44.0.0.0/9 lookup main priority 5 
    ip rule del to 44.128.0.0/10 lookup main priority 6     
    ip route del 44.0.0.0/9 via 44.x.y.A
    ip route del 44.128.0.0/10 via 44.x.y.A
    ip route del 44.x.y.z/29 dev br-lan
    
    gw="$(get_wlan1_gw)"
    ip route del 44.a.b.c via "$gw" dev wlan1 
    
    logger -t customroutes "Custom IP routes removed"
}
</code></pre></div></div>

<p>Inside the <code class="language-plaintext highlighter-rouge">start()</code> function, add these lines as needed from the IP forwarding and RP filtering step above:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 0 &gt; /proc/sys/net/ipv4/conf/&lt;name&gt;/rp_filter
echo 1 &gt; /proc/sys/net/ipv4/ip_forward 
</code></pre></div></div>

<p>Enable the service to run at startup on the AREDN node (although the node might not respect it):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x /etc/init.d/customroutes
/etc/init.d/customroutes enable
</code></pre></div></div>

<h3 id="scripting-this-from-the-rasperry-pi">Scripting This from the Rasperry Pi</h3>

<p>In case the AREDN router fails to execute this on startup, I also made a script on the Raspberry Pi to execute these <code class="language-plaintext highlighter-rouge">ip route</code> commands via ssh.</p>

<p>On the pi, create a file <code class="language-plaintext highlighter-rouge">/usr/local/bin/setup-mesh-routes.sh</code> with the same commands and addresses as above:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash

# Define remote host and port
REMOTE_HOST="root@localnode.local.mesh"
REMOTE_PORT=2222

# Run the routing commands over SSH
ssh -p $REMOTE_PORT $REMOTE_HOST &lt;&lt; 'EOF'
ip rule add to 44.0.0.0/9 lookup main priority 5 || true
ip rule add to 44.128.0.0/10 lookup main priority 6 || true
ip route add 44.0.0.0/9 via 44.x.y.A || true
ip route add 44.128.0.0/10 via 44.x.y.A || true
ip route add 44.x.y.z/29 dev br-lan || true

set -- $(ip -4 route show default dev wlan1)
gw="$3"
ip route add 44.a.b.c via "$gw" dev wlan1 || true
EOF
</code></pre></div></div>

<p>Similarly, add these lines as needed for the IP forwarding and RP filtering steps from earlier:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 0 &gt; /proc/sys/net/ipv4/conf/&lt;name&gt;/rp_filter
echo 1 &gt; /proc/sys/net/ipv4/ip_forward 
</code></pre></div></div>

<p>Executing <code class="language-plaintext highlighter-rouge">/usr/local/bin/setup-mesh-routes.sh</code> from the raspberry pi will set up the routes.  The <code class="language-plaintext highlighter-rouge">|| true</code> clause at each line allows the script to continue and exit successfully even if the routes already exist on the router.</p>

<h4 id="automating-the-script-to-run-at-startup">Automating the Script to Run at Startup</h4>

<p>If the AREDN mesh could retain ssh keys for logging in, I could fully automate this by enabling an <code class="language-plaintext highlighter-rouge">init.d</code> service on the raspberry pi to execute this script whenever the wireless LAN becomes available.  However, since this requires a password, I ssh into the pi and run this script myself.  If you are willing to store your AREDN root password on your pi, you could pass it through the script to call <code class="language-plaintext highlighter-rouge">ssh</code>.  The <code class="language-plaintext highlighter-rouge">sshpass</code> program does exactly this, and you can replace the line <code class="language-plaintext highlighter-rouge">ssh -p $REMOTE_PORT $REMOTE_HOST &lt;&lt; 'EOF'</code> with:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -p $REMOTE_PORT $REMOTE_HOST &lt;&lt; 'EOF' 
</code></pre></div></div>

<p>Assuming you’ve set the <code class="language-plaintext highlighter-rouge">PASSWORD</code> environment variable in the script, from a file, or from the outside environment.</p>

<p>In that case, this can be established as a startup service on the pi:</p>

<p>Create the file <code class="language-plaintext highlighter-rouge">/etc/system/system/aredn-routing.service</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Configure IP routes for AREDN on boot
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-mesh-routes.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
</code></pre></div></div>

<p>Enable it to run at startup via:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable aredn-routing.service
sudo systemctl start aredn-routing.service
</code></pre></div></div>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="hamradio" /><summary type="html"><![CDATA[This guide will walk you through setting up a Mikrotik hAp device (I used a hAp ac2) to use 44net addresses, bridging AREDN and 44net services between the two networks. I set up the hAp to broadcast a WiFi hotspot SSID that, when connected to a client, enables access to both 44net and to AREDN resources simultaneously. I use 44net Connect (formerly 44net.cloud) to route a network allocation they assigned to me through a Wireguard tunnel that they also assigned. The tunnel can be configured through their portal to route to the network. It is likely also possible to do this by decapsulating the ipencap packets from the raspberry pi directly, and using a traditional 44net subnet allocation, but this setup enables me to take the hAp setup to mobile deployments, without worring about the NAT configuration or my ability to forward ipencap traffic at my destination.]]></summary></entry><entry><title type="html">Setting Up 44net on a Mikrotik Using 44net.cloud for ipencap Forwarding</title><link href="https://www.billmongan.com/posts/2025/06/44net/" rel="alternate" type="text/html" title="Setting Up 44net on a Mikrotik Using 44net.cloud for ipencap Forwarding" /><published>2025-06-28T00:00:00+00:00</published><updated>2025-06-28T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2025/06/44net</id><content type="html" xml:base="https://www.billmongan.com/posts/2025/06/44net/"><![CDATA[<p>This guide will walk you through setting up a Mikrotik router with a 44net network allocation using Wireguard to <a href="https://www.44net.cloud">44net.cloud</a> in order to receive ipip encapsulated packets from the UCSD 44net router.  This way, you do not need to be able to forward these packets through your home router or have native IP Protocol 4 support to access 44net.  In this setup, I used a Mikrotik hAp ac2 lite.</p>

<h2 id="configure-44netcloud-and-44net-network">Configure 44net.cloud and 44net network</h2>

<p>On the <a href="https://portal.ampr.org">AMPR portal</a>, request a subnet, and on 44net.cloud, request a tunnel.</p>

<p>Once you receive the tunnel, go back to the AMPR portal, and under <code class="language-plaintext highlighter-rouge">Networks</code> - <code class="language-plaintext highlighter-rouge">My Gateways</code>, click <code class="language-plaintext highlighter-rouge">Create a Gateway</code>.  Fill in the 44net public IP address of your 44net.cloud tunnel as well as the DNS record for that IP address.  You can use <code class="language-plaintext highlighter-rouge">nslookup</code> to determine if a DNS entry exists, or create your own with a third party service and use that domain name here.</p>

<p>Link this gateway to your 44net subnet allocation.</p>

<h2 id="configure-ampr-portal-dns-record-for-your-mikrotik-gateway">Configure AMPR Portal DNS Record for your Mikrotik gateway</h2>

<p>On the AMPR portal, and under <code class="language-plaintext highlighter-rouge">DNS</code> - <code class="language-plaintext highlighter-rouge">My Subdomains</code>, select your subdomain (or create one if needed).  Click <code class="language-plaintext highlighter-rouge">Add a resource record</code> and give a hostname like <code class="language-plaintext highlighter-rouge">gw</code> that points to an A record of your gateway IP address (the first usable address within your 44net subdomain; so <code class="language-plaintext highlighter-rouge">44.x.y.1</code> if your network is <code class="language-plaintext highlighter-rouge">44.x.y.0/29</code>.</p>

<h2 id="configuring-the-mikrotik">Configuring the Mikrotik</h2>

<p>Log into your Mikrotik device.</p>

<h3 id="establishing-wireguard-connection-to-44netcloud">Establishing Wireguard Connection to 44net.cloud</h3>

<p>Execute the following commands in terminal mode to set up your Wireguard connection to 44net.cloud, assuming <code class="language-plaintext highlighter-rouge">44.X.Y.Z/32</code> is your 44net.cloud tunnel IP address:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 1. Add the WireGuard interface - PUT YOUR WIREGUARD PRIVATE KEY BELOW
/interface/wireguard/add name=wg-44net listen-port=13231 private-key="YOUR PRIVATE KEY HERE"

# 2. Assign the IP address to the interface - PUT YOUR 44NET.CLOUD 44net IP ADDRESS BELOW
/ip/address/add address=44.X.Y.Z/32 interface=wg-44net

# 3. Set the DNS server (optional, for local resolver usage)
/ip/dns/set servers=1.1.1.1

# 4. Add the WireGuard peer - PUT YOUR 44NET.CLOUD PEER PUBLIC KEY, PRESHARED KEY, ENDPOINT ADDRESS, and PORT BELOW
/interface/wireguard/peers/add interface=wg-44net \
    public-key="YOUR PUBLIC KEY HERE" \
    preshared-key="YOUR PRESHARED KEY HERE" \
    endpoint-address=44.X.Y.Z \
    endpoint-port=YOUR 44NET.CLOUD PORT HERE \
    allowed-address=44.0.0.0/9,44.128.0.0/10,169.228.34.84 \
    persistent-keepalive=20

# 5. Configure traffic
/ip firewall filter add chain=input action=accept in-interface=wg-44net comment="Allow input from wg-44net" place-before=5

/ip route add dst-address=44.0.0.0/9 gateway=wg-44net comment="44Net outbound routing"
/ip route add dst-address=44.128.0.0/10 gateway=wg-44net comment="44Net outbound routing"
</code></pre></div></div>

<p>Here, we configure the wireguard connection to allow 44net traffic as well as traffic to the <code class="language-plaintext highlighter-rouge">amprgw</code> at UCSD.</p>

<h3 id="configure-wan-options">Configure WAN Options</h3>

<p>My hAp ac2 has two wireless radios on 2.4 and 5 ghz (<code class="language-plaintext highlighter-rouge">wlan1</code> and <code class="language-plaintext highlighter-rouge">wlan2</code>, respectively).  Optionally, you can configure your node as a WiFi client or hotspot to connect to the Internet or to create a hotspot.  If you configure a WiFi client, be sure it (i.e., <code class="language-plaintext highlighter-rouge">wlan2</code>) is acting as a DHCP client in addition to the <code class="language-plaintext highlighter-rouge">ether1</code> port.</p>

<p>Here, we will configure <code class="language-plaintext highlighter-rouge">wlan1</code> as a client and put it on the bridge with our ethernet LAN ports, and then add <code class="language-plaintext highlighter-rouge">ether1</code> as a DHCP client acting as the WAN port.  I will also set <code class="language-plaintext highlighter-rouge">wlan2</code> to act as a WiFi client so that I can connect to the internet.</p>

<p>First, we will set up the bridge to include the ethernet LAN ports:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
</code></pre></div></div>

<h4 id="configure-wifi-hotspot">Configure WiFi Hotspot</h4>

<p><code class="language-plaintext highlighter-rouge">wlan1</code> can be configured as a wireless access point hotspot so that clients can connect to it and receive 44net IP addresses as if they were plugged into a LAN ethernet port.  Set <code class="language-plaintext highlighter-rouge">wlan1</code> to <code class="language-plaintext highlighter-rouge">ap-bridge</code> mode and associate it with an SSID name and security profile with your desired pre-shared keys.  Then add it to the bridge:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/interface bridge port
add bridge=bridge comment=defconf interface=wlan1
</code></pre></div></div>

<h4 id="configure-wifi-client">Configure WiFi Client</h4>

<p><code class="language-plaintext highlighter-rouge">wlan2</code> can be used as an WiFi client (<code class="language-plaintext highlighter-rouge">station</code> mode) for outbound connections from your clients to the Internet via an existing base station.  I configured <code class="language-plaintext highlighter-rouge">wlan2</code> as a client, and associated it with a security profile that included my pre-shared keys for the wireless network I was connecting to (similar to the WiFi hotspot I established on <code class="language-plaintext highlighter-rouge">wlan1</code>).  I set <code class="language-plaintext highlighter-rouge">wlan2</code> to be a wireless <code class="language-plaintext highlighter-rouge">station</code>, and set <code class="language-plaintext highlighter-rouge">wlan2</code> as a DHCP client.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/interface wireless set [find name=wlan2] mode=station
/ip dhcp-client add interface=wlan2 add-default-route=yes default-route-distance=1 use-peer-dns=yes use-peer-ntp=yes disabled=no
/interface list member
add interface=wlan2 list=WAN comment="Wi-Fi uplink is WAN too"
</code></pre></div></div>

<h4 id="configure-dhcp">Configure DHCP</h4>

<p>Set up your router’s DHCP server to use the 44net subnet you’ve been allocated by AMPR, filling in your subnet information (i.e., <code class="language-plaintext highlighter-rouge">44.x.y.0/29</code>), gateway IP (i.e., <code class="language-plaintext highlighter-rouge">44.x.y.1</code>), and usable IP addresses (i.e., <code class="language-plaintext highlighter-rouge">44.x.y.1-44.x.y.6</code>) below.  We will also set the <code class="language-plaintext highlighter-rouge">ether1</code> WAN port as a DHCP client.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip pool
add name=dhcp ranges=44.x.y.1-44.x.y.6
/ip dhcp-server
add address-pool=dhcp interface=bridge name=defconf
/ip dhcp-server network
add address=44.x.y.0/29 gateway=44.x.y.1 dns-server=44.x.y.1
/ip dhcp-client
add interface=ether1 comment=defconf
</code></pre></div></div>

<h3 id="assigning-ip-addresses-from-your-44net-subnet-and-from-44netcloud">Assigning IP addresses from your 44net Subnet and from 44net.cloud</h3>

<p>Assign an IP address for the gateway to internal network bridge.  We assume your gateway IP is <code class="language-plaintext highlighter-rouge">44.x.y.1</code> from your AMPR 44net subnet allocation.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip address
add address=44.x.y.1/29 interface=bridge comment=defconf
</code></pre></div></div>

<h3 id="create-an-ipip-tunnel-to-the-ucsd-44net-gateway">Create an IPIP Tunnel to the UCSD 44net Gateway</h3>

<p>In order to receive IPIP packets to your gateway from UCSD, you will need an IPIP tunnel interface to UCSD (using the IP address associated with <code class="language-plaintext highlighter-rouge">amprgw.ucsd.edu</code>) and from your 44net.cloud IP tunnel address <code class="language-plaintext highlighter-rouge">44.X.Y.Z</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/interface ipip
add name=ipip-44net local-address=44.X.Y.Z remote-address=169.228.34.84 allow-fast-path=no
/ip address add address=44.X.Y.Z/32 interface=ipip-44net
/ip firewall mangle
add chain=forward tcp-flags=syn protocol=tcp out-interface=ipip-44net \
    action=change-mss new-mss=clamp-to-pmtu comment="Clamp MSS for IPIP-over-WG on egress"
add chain=forward action=change-mss protocol=tcp tcp-flags=syn in-interface=wg-44net \
    new-mss=clamp-to-pmtu comment="Clamp MSS when ingressing WG"    
</code></pre></div></div>

<h3 id="configuring-the-basic-routing-table">Configuring the Basic Routing Table</h3>

<p>To route traffic over the Wireguard interface and over the IPIP tunnel, add the following routing tables and routes.  Here, we assume <code class="language-plaintext highlighter-rouge">44.x.y.z</code> is your 44net.cloud tunnel IP address, and <code class="language-plaintext highlighter-rouge">44.0.0.0/29</code> is your AMPR 44net subnet allocation.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/routing table
add name=via-wg
add name=via-ipip
add fib name=via-wg
add fib name=via-ipip

/ip route
add dst-address=44.0.0.0/9 gateway=wg-44net comment="Send all AMPRNet traffic via WireGuard"
add dst-address=44.128.0.0/10 gateway=wg-44net comment="Send all AMPRNet traffic via WireGuard"
add dst-address=0.0.0.0/0 gateway=wg-44net routing-table=via-wg comment="Send all traffic that came in over Wireguard back out via Wireguard"
add dst-address=0.0.0.0/0 gateway=ipip-44net routing-table=via-ipip comment="Send all traffic that came in over the IPIP tunnel back out via the IPIP tunnel"
add dst-address=169.228.34.84/32 out-interface=wg-44net comment="UCSD IPIP transport via WG"
    
/routing rule
add src-address=44.x.y.z/32 interface=wg-44net action=lookup table=via-wg 
add src-address=44.x.y.z/32 interface=ipip-44net action=lookup table=via-ipip
add src-address=44.0.0.0/29 interface=wg-44net action=lookup table=via-wg
add src-address=44.0.0.0/29 interface=ipip-44net action=lookup table=via-ipip
</code></pre></div></div>

<h3 id="configuring-basic-firewall-rules">Configuring Basic Firewall Rules</h3>

<p>You’ll want to add additional rules to harden this installation!  These simply make the tunnel connections functional, and these rules <strong>are not intended to secure the network from the outside</strong>.  Here, we assume <code class="language-plaintext highlighter-rouge">44.x.y.0/29</code> is your 44net subnet allocation from AMPR, <code class="language-plaintext highlighter-rouge">44.x.y.1</code> is your gateway IP address and that <code class="language-plaintext highlighter-rouge">44.x.y.2-44.x.y.6</code> are your remaining routable IP addresses on your subnet.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip firewall filter
add chain=input action=accept connection-state=established,related,untracked comment="Allow established"
add chain=input action=drop connection-state=invalid comment="Drop invalid"
add chain=input action=accept protocol=icmp comment="Allow ICMP"
add chain=input action=accept in-interface=wg-44net comment="Allow input from wg-44net"
add chain=input action=accept dst-address=44.x.y.1 protocol=icmp comment="Allow ICMP to 44Net address"
add chain=input action=drop in-interface-list=!LAN comment="Drop non-LAN traffic"
add chain=input in-interface=wg-44net protocol=ipip action=accept comment="Allow inbound IPIP via WG"

add chain=forward action=accept connection-state=established,related,untracked
add chain=forward action=drop connection-state=invalid
add chain=forward action=fasttrack-connection connection-state=established,related hw-offload=yes comment="FastTrack"
add chain=forward action=accept dst-address=44.x.y.2-44.x.y.6 comment="Inbound to 44Net hosts"
add chain=forward action=accept dst-address=44.0.0.0/9 src-address=44.x.y.0/29
add chain=forward action=accept dst-address=44.128.0.0/10 src-address=44.x.y.0/29
add chain=forward action=accept dst-address=44.x.y.0/29 in-interface=wg-44net
add chain=forward action=accept out-interface=wg-44net src-address=44.x.y.0/29
add chain=forward action=accept dst-address=44.x.y.0/29 in-interface=ipip-44net
add chain=forward action=accept out-interface=ipip-44net src-address=44.x.y.0/29
add chain=forward out-interface=ipip-44net action=accept comment="Allow outbound to IPIP tunnel"

/ip/firewall/connection/tracking/set enabled=yes
</code></pre></div></div>

<h3 id="configuring-dns">Configuring DNS</h3>

<p>Enable outside DNS lookups as follows (feel free to replace your favorite DNS servers for <code class="language-plaintext highlighter-rouge">1.1.1.1,8.8.8.8</code>.  We assume <code class="language-plaintext highlighter-rouge">44.x.y.1</code> is your gateway IP.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip dns
set allow-remote-requests=yes servers=1.1.1.1,8.8.8.8
/ip dns static
add name=router.lan address=44.x.y.1 type=A comment=defconf
</code></pre></div></div>

<h3 id="adding-44net-network-gateways-to-your-routing-table">Adding 44net Network Gateways to your Routing Table</h3>

<p>You can route traffic over the IPIP tunnel directly to other 44net subnets.  To do this, you need to be aware of their routing table.  UCSD sends this routing table periodically to all nodes, but there is not an easy way to process them on Mikrotik routers once they’re received.  Instead, we can create them manually, and flush/re-create the table on-demand to receive any updates.</p>

<p>This Python program can be run on your local computer, and it generates a Mikrotik script that you can upload and run on your router to populate these routing entries.  They are all tagged with <code class="language-plaintext highlighter-rouge">ampr-imported-route</code> so that they can be flushed for re-creation without modifying the rest of your routing rules.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">ipaddress</span>

<span class="c1"># Upload output file to router and execute with /import file-name=ampr_routes.rsc
</span>
<span class="n">API_TOKEN</span> <span class="o">=</span> <span class="s">"YOUR AMPR PORTAL API TOKEN FROM YOUR AMPR PORTAL PROFILE PAGE"</span>
<span class="n">OUTPUT_FILE</span> <span class="o">=</span> <span class="s">"ampr_routes.rsc"</span>
<span class="n">GATEWAY</span> <span class="o">=</span> <span class="s">"ipip-44net"</span> <span class="c1"># or none to use their address, if routing over ipip already for these IP ranges
</span><span class="n">ROUTE_COMMENT</span> <span class="o">=</span> <span class="s">"ampr-imported-route"</span>

<span class="c1"># List of local subnets to skip (any CIDR you handle internally)
</span><span class="n">SKIP_PREFIXES</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"44.x.y.0/29"</span><span class="p">,</span>     <span class="c1"># Your allocation
</span><span class="p">]</span>
<span class="n">SKIP_NETWORKS</span> <span class="o">=</span> <span class="p">[</span><span class="n">ipaddress</span><span class="p">.</span><span class="n">ip_network</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">SKIP_PREFIXES</span><span class="p">]</span>

<span class="c1"># Fetch route list from AMPRNet
</span><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span>
    <span class="s">"https://portal.ampr.org/api/v2/encap/routes"</span><span class="p">,</span>
    <span class="n">headers</span><span class="o">=</span><span class="p">{</span>
        <span class="s">"Authorization"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Bearer </span><span class="si">{</span><span class="n">API_TOKEN</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
        <span class="s">"Accept"</span><span class="p">:</span> <span class="s">"application/json"</span>
    <span class="p">}</span>
<span class="p">)</span>

<span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">routes</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"encap"</span><span class="p">,</span> <span class="p">[])</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">OUTPUT_FILE</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="c1"># First, remove all old AMPR routes by comment
</span>    <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s">"/ip route remove [find comment=</span><span class="se">\"</span><span class="si">{</span><span class="n">ROUTE_COMMENT</span><span class="si">}</span><span class="se">\"</span><span class="s">]</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># Now add the updated routes
</span>    <span class="k">for</span> <span class="n">route</span> <span class="ow">in</span> <span class="n">routes</span><span class="p">:</span>
        <span class="n">prefix</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">route</span><span class="p">[</span><span class="s">'network'</span><span class="p">]</span><span class="si">}</span><span class="s">/</span><span class="si">{</span><span class="n">route</span><span class="p">[</span><span class="s">'cidr'</span><span class="p">]</span><span class="si">}</span><span class="s">"</span>
        <span class="n">leaseholder</span> <span class="o">=</span> <span class="n">route</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"leaseholder"</span><span class="p">,</span> <span class="s">"unknown"</span><span class="p">)</span>
        
        <span class="c1"># Skip if the prefix overlaps any local subnet
</span>        <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">ipaddress</span><span class="p">.</span><span class="n">ip_network</span><span class="p">(</span><span class="n">prefix</span><span class="p">).</span><span class="n">overlaps</span><span class="p">(</span><span class="n">local</span><span class="p">)</span> <span class="k">for</span> <span class="n">local</span> <span class="ow">in</span> <span class="n">SKIP_NETWORKS</span><span class="p">):</span>
            <span class="k">continue</span>        
            
        <span class="k">if</span> <span class="n">GATEWAY</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">gateway</span> <span class="o">=</span> <span class="n">route</span><span class="p">[</span><span class="s">'gatewayIP'</span><span class="p">]</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">gateway</span> <span class="o">=</span> <span class="n">GATEWAY</span>
        
        <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span>
            <span class="sa">f</span><span class="s">"/ip route add dst-address=</span><span class="si">{</span><span class="n">prefix</span><span class="si">}</span><span class="s"> gateway=</span><span class="si">{</span><span class="n">gateway</span><span class="si">}</span><span class="s"> comment=</span><span class="se">\"</span><span class="si">{</span><span class="n">ROUTE_COMMENT</span><span class="si">}</span><span class="se">\"\n</span><span class="s">"</span>
        <span class="p">)</span>
</code></pre></div></div>

<p>Upload the <code class="language-plaintext highlighter-rouge">ampr_routes.rsc</code> file to your Mikrotik router under the <code class="language-plaintext highlighter-rouge">Files</code> tab, and back in the terminal, execute the following command to run the script and add the routes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/import file-name=ampr_routes.rsc
</code></pre></div></div>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>Reboot the router and wait an hour for UCSD to start sending packets to your gateway.</p>

<p>Test by pinging your gateway (<code class="language-plaintext highlighter-rouge">gw.&lt;your call sign&gt;.ampr.org</code> or <code class="language-plaintext highlighter-rouge">44.x.y.1</code>).</p>

<p>Be sure to set firewall rules to restrict traffic, especially inbound, to your router!</p>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="hamradio" /><summary type="html"><![CDATA[This guide will walk you through setting up a Mikrotik router with a 44net network allocation using Wireguard to 44net.cloud in order to receive ipip encapsulated packets from the UCSD 44net router. This way, you do not need to be able to forward these packets through your home router or have native IP Protocol 4 support to access 44net. In this setup, I used a Mikrotik hAp ac2 lite.]]></summary></entry><entry><title type="html">Setting Up AllStarLink ASL3 and Associated Tools</title><link href="https://www.billmongan.com/posts/2025/01/asl3/" rel="alternate" type="text/html" title="Setting Up AllStarLink ASL3 and Associated Tools" /><published>2025-01-10T00:00:00+00:00</published><updated>2025-01-10T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2025/01/asl3</id><content type="html" xml:base="https://www.billmongan.com/posts/2025/01/asl3/"><![CDATA[<p>This guide walks you through installing and configuring AllStarLink (ASL3) along with several helpful management tools and utilities:</p>

<ul>
  <li>allscan</li>
  <li>supermon</li>
  <li>allmon3</li>
  <li>skywarn plus</li>
  <li>dvswitch</li>
  <li>digital_link</li>
  <li>alltune</li>
</ul>

<h2 id="step-1-install-asl3">Step 1: Install ASL3</h2>

<ol>
  <li><strong>Prepare the Raspberry Pi</strong>:
    <ul>
      <li>Download and install the Raspberry Pi Imager.</li>
      <li>Select Lite 64-bit OS and configure username, password, and enable SSH.</li>
      <li>Flash the microSD card and boot the Raspberry Pi.</li>
    </ul>
  </li>
  <li><strong>Install ASL3</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> <span class="nt">-s</span>
apt update <span class="o">&amp;&amp;</span> apt upgrade
<span class="nb">cd</span> /tmp
wget https://repo.allstarlink.org/public/asl-apt-repos.deb12_all.deb
dpkg <span class="nt">-i</span> asl-apt-repos.deb12_all.deb
apt update
apt <span class="nb">install </span>git asl3
</code></pre></div>    </div>
  </li>
  <li><strong>Configure ASL3</strong>:
    <ul>
      <li>Run the ASL configuration menu:
        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>asl-menu
</code></pre></div>        </div>
      </li>
      <li>Adjust node settings as needed and save.</li>
      <li>For my setup, I set CTCSS From to <code class="language-plaintext highlighter-rouge">usb</code> instead of <code class="language-plaintext highlighter-rouge">usbinvert</code></li>
    </ul>
  </li>
</ol>

<p>Access ASL3 at <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;:9090</code>.</p>

<h2 id="step-2-configure-asl3-components">Step 2: Configure ASL3 Components</h2>

<h3 id="simpleusb-configuration">SimpleUSB Configuration</h3>
<p>Edit the <code class="language-plaintext highlighter-rouge">simpleusb.conf</code> file to set up your hardware interface:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano /etc/asterisk/simpleusb.conf
</code></pre></div></div>

<h3 id="configure-audio-levels">Configure Audio Levels</h3>
<p>Restart Asterisk and test the setup:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>asterisk <span class="nt">-r</span>
rpt fun &lt;your_node_number&gt; <span class="k">*</span>355553
</code></pre></div></div>

<p>Speak into the microphone and adjust the audio settings so that the meter just touches the 5kHz level.  Disconnect when done with:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rpt fun &lt;your_node_number&gt; <span class="k">*</span>155553
</code></pre></div></div>

<p>To set the audio transmit level, edit <code class="language-plaintext highlighter-rouge">/etc/asterisk/simpleusb.conf</code> and set the <code class="language-plaintext highlighter-rouge">rxmixerset</code> value.  I went with a value around <code class="language-plaintext highlighter-rouge">650</code>, such that running <code class="language-plaintext highlighter-rouge">asterisk -rvvv</code> (after restarting asterisk) and <code class="language-plaintext highlighter-rouge">rpt debug level 7</code> decodes and displays DTMF codes being sent and audio levels “just about right” or at least just a bit low when testing on node <code class="language-plaintext highlighter-rouge">55553</code>.  I found that setting the audio level to the “just about right” setting caused the DTMF tones to be oversaturated and fail to decode, so I adjusted this to a slightly lower value.</p>

<h3 id="test-allstarlink-connection">Test AllStarLink Connection</h3>

<p>Connect to Allstar Echo node: <code class="language-plaintext highlighter-rouge">40894</code> via the radio by entering <code class="language-plaintext highlighter-rouge">*340894</code> to connect (and speak / echo), and <code class="language-plaintext highlighter-rouge">*140894</code> to disconnect.</p>

<h3 id="allmon3-setup">Allmon3 Setup</h3>
<ol>
  <li>Install Allmon3:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>allmon3
</code></pre></div>    </div>
  </li>
  <li>
    <p>Configure Allmon3 settings in <code class="language-plaintext highlighter-rouge">/etc/allmon3/allmon3.ini</code> according to <a href="https://github.com/VALER24/allstar-shari-dvswitch-install-guide">these instructions</a>.  Be sure to remove the <code class="language-plaintext highlighter-rouge">allmon3</code> user account and add an admin password:</p>

    <p>Update passwords:</p>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>allmon3-passwd <span class="nt">--delete</span> allmon3
allmon3-passwd admin
</code></pre></div>    </div>

    <p>Uncomment / set account and <code class="language-plaintext highlighter-rouge">manager.conf</code> secret for node 1999.</p>
  </li>
  <li>Restart services:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart asterisk
systemctl start allmon3
</code></pre></div>    </div>
  </li>
</ol>

<p>Access Allmon3 at <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;/allmon3</code>.</p>

<h3 id="echolink-setup">Echolink Setup</h3>
<p>Edit the following files to configure Echolink: <code class="language-plaintext highlighter-rouge">/etc/asterisk/echolink.conf</code> and <code class="language-plaintext highlighter-rouge">/etc/asterisk/modules.conf</code>.</p>

<p>In <code class="language-plaintext highlighter-rouge">/etc/asterisk/modules.conf</code>, add or uncomment this line: <code class="language-plaintext highlighter-rouge">load =&gt; chan_echolink.so</code>.  If there is a similar line with <code class="language-plaintext highlighter-rouge">noload =&gt;</code>, comment that out.</p>

<p>In /etc/asterisk/echolink.conf, add your AllStarLink node number and set your personal EchoLink information.</p>

<p>Load the Echolink module and restart Asterisk:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart asterisk
</code></pre></div></div>

<p>Test by connecting to <code class="language-plaintext highlighter-rouge">*33009999</code> (EchoLink echo test 9999); then <code class="language-plaintext highlighter-rouge">*13009999</code> to disconnect.  To connect to an EchoLink node, dial <code class="language-plaintext highlighter-rouge">*33</code> followed by a 6 digit EchoLink node number.  If the node number is fewer than 6 digits long, prepend the node number with enough zeroes to make a 6 digit number.  For example, EchoLink node <code class="language-plaintext highlighter-rouge">9999</code> is entered as <code class="language-plaintext highlighter-rouge">009999</code>.</p>

<h2 id="step-3-install-and-configure-dvswitch">Step 3: Install and Configure DVSwitch</h2>
<ol>
  <li>
    <p>Install DVSwitch:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://dvswitch.org/bookworm
<span class="nb">chmod</span> +x bookworm
./bookworm
apt update
apt <span class="nb">install </span>dvswitch-server
apt <span class="nb">install </span>php-cgi libapache2-mod-php8.2
</code></pre></div>    </div>
  </li>
  <li>
    <p>Configure DVSwitch:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /usr/local/dvs
./dvs
</code></pre></div>    </div>

    <p>You can set modes from the <code class="language-plaintext highlighter-rouge">dvs</code> menu under <code class="language-plaintext highlighter-rouge">Advanced - Configure Other Stanzas (Edit &lt;mode&gt;)</code>, <code class="language-plaintext highlighter-rouge">Additional DMR Networks</code>, and <code class="language-plaintext highlighter-rouge">Configure Favorite TG</code>.  Additionally, you may be able to set up DV3000 USB as the vocoder here if available for D-Star support under the initial setup menu, in the <code class="language-plaintext highlighter-rouge">Hardware Vocoder</code> section (for example, if you have a USB ThumbDV AMBE vocoder device).</p>
  </li>
  <li>
    <p>Edit configuration files as needed:
In <code class="language-plaintext highlighter-rouge">/opt/Analog_Bridge/Analog_Bridge.ini</code>, under <code class="language-plaintext highlighter-rouge">[USRP]</code>, set:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>txPort = 32001                          ; Transmit USRP frames on this port
rxPort = 34001                          ; Listen for USRP frames on this port
usrpAudio  AUDIO_USE_GAIN
usrpGain = 3.00
tlvAudio = AUDIO_USE_GAIN
</code></pre></div>    </div>

    <p>In <code class="language-plaintext highlighter-rouge">/opt/MMDVM_Bridge/MMDVM_Bridge.ini</code>, set <code class="language-plaintext highlighter-rouge">Jitter=750</code> under <code class="language-plaintext highlighter-rouge">[DMR Network]</code>, and set  <code class="language-plaintext highlighter-rouge">RX/TXFrequency</code> to <code class="language-plaintext highlighter-rouge">433800000</code> under <code class="language-plaintext highlighter-rouge">[Info]</code>.  You can also set your contact information in this file.</p>
  </li>
  <li>
    <p>Set up private node <code class="language-plaintext highlighter-rouge">1999</code> in ASL3 to bridge to DVSwitch:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">sudo </span>asl-menu
</code></pre></div>    </div>

    <p>Under <code class="language-plaintext highlighter-rouge">Node Settings - AllStar Node Settings - Add Node</code> with the following parameters: node number 1999, “None of the Above”, “Radio Interface USRP”, and “Duplex Type 0 / Half”.</p>

    <p>Test by connecting/disconnecting to node 1999 in allmon3.  You must connect your ASL node to 1999 before using DVSwitch.  You can dial <code class="language-plaintext highlighter-rouge">*31999</code> to do this (and <code class="language-plaintext highlighter-rouge">*11999</code> to disconnect).</p>
  </li>
  <li>Enable and start DVSwitch services:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>analog_bridge mmdvm_bridge md380-emu
systemctl start analog_bridge mmdvm_bridge md380-emu
</code></pre></div>    </div>
  </li>
  <li>
    <p>Test DVSwitch:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/MMDVM_Bridge/dvswitch.sh mode YSF
/opt/MMDVM_Bridge/dvswitch.sh tune parrot.ysfreflector.de:42020
</code></pre></div>    </div>

    <p>In general, you can connect to any mode and endpoint via:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/MMDVM_Bridge/dvswitch.sh mode <span class="o">{</span>DMR|NXDN|P25|YSF|DSTAR<span class="o">}</span> <span class="c"># Set Analog_Bridge digital mode</span>
/opt/MMDVM_Bridge/dvswitch.sh tune &lt;tg&gt; <span class="c"># Tune to specific TG number/Reflector</span>
</code></pre></div>    </div>

    <p>Don’t forget to connect to node 1999 either through DTMF tones, or through allmon3.  You can change digital modes using the web interface at <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;/dvswitch/index.php</code>.</p>

    <p>Note that DVSwitch cannot parse the <code class="language-plaintext highlighter-rouge">#</code> private call character in a <code class="language-plaintext highlighter-rouge">tune</code> command using the <code class="language-plaintext highlighter-rouge">/opt/MMDVM_Bridge/dvswitch.sh tune yourbrandmeisterpasswordhere@3102.repeater.net:62031!91</code> format (i.e., using <code class="language-plaintext highlighter-rouge">!9990#</code>).  Instead, run these as two commands:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/MMDVM_Bridge/dvswitch.sh tune yourbrandmeisterpasswordhere@3102.repeater.net:62031
/opt/MMDVM_Bridge/dvswitch.sh tune '9990#'
</code></pre></div>    </div>

    <p>The <code class="language-plaintext highlighter-rouge">!</code> format seems to work when connecting to a non-private call talkgroup; however, it should be separated for any private calls requiring a <code class="language-plaintext highlighter-rouge">#</code> at the end (including the <code class="language-plaintext highlighter-rouge">4000#</code> unlink command.</p>
  </li>
  <li>
    <p>Enable DVSwitch Mode Switcher Frontend:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">sudo </span>apt update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt upgrade <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>git nodejs
 git clone https://github.com/firealarmss/dvswitch_mode_switcher.git
 <span class="nb">cd </span>dvswitch_mode_switcher
</code></pre></div>    </div>

    <p>Follow the instructions in dvswitch_mode_switcher-README.md.txt within the above repository.  I also set <code class="language-plaintext highlighter-rouge">enabled: true</code> under <code class="language-plaintext highlighter-rouge">usrp</code> in <code class="language-plaintext highlighter-rouge">/opt/dvswitch_mode_switcher/configs/config.yml</code>.</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">cd</span> /opt/dvswitch_mode_switcher
 <span class="nb">cp </span>debian/dvswitch_mode_switcher.service /etc/systemd/system/dvswitch_mode_switcher.service
 systemctl daemon-reload
 systemctl <span class="nb">enable </span>dvswitch_mode_switcher.service
 systemctl start dvswitch_mode_switcher.service
</code></pre></div>    </div>

    <p>Then, log onto the ASL3 Cockpit at <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;:9090</code> to update the firewall to allow port <code class="language-plaintext highlighter-rouge">3000</code> access.  After logging in, click <code class="language-plaintext highlighter-rouge">Networking</code> on the left, click <code class="language-plaintext highlighter-rouge">Edit Rules and Zones</code> in the firewall panel, click <code class="language-plaintext highlighter-rouge">Custom Ports</code>, and enable port <code class="language-plaintext highlighter-rouge">3000</code> over <code class="language-plaintext highlighter-rouge">TCP</code>.  Alternatively, I logged in over ssh and restricted access to a particular IP block (instead of adding it through the web interface, which opens the port to all IP addresses), by running:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cat &lt;&lt;EOF &gt; /usr/local/bin/nft-allow-allstarlink.sh
 #!/bin/bash
 CHAIN_NAME="filter_IN_allstarlink_allow"
 TABLE_NAME="inet firewalld"

 # Wait up to 30 seconds for firewalld to create the required chain
 for i in {1..30}; do
     if /usr/sbin/nft list chain $TABLE_NAME $CHAIN_NAME &gt;/dev/null 2&gt;&amp;1; then
         break
     fi
     /usr/bin/sleep 1
 done

 # If the chain still doesn't exist, fail
 if ! /usr/sbin/nft list chain $TABLE_NAME $CHAIN_NAME &gt;/dev/null 2&gt;&amp;1; then
     echo "Error: firewalld chain $CHAIN_NAME not found after timeout"
     exit 1
 fi

 /usr/sbin/nft add rule inet firewalld filter_IN_allstarlink_allow ip saddr &lt;your net address&gt;/&lt;your subnet i.e. 24&gt; tcp dport 3000 accept
 EOF

 chmod +x /usr/local/bin/nft-allow-allstarlink.sh

 mkdir -p /etc/systemd/system/firewalld.service.d

 cat &lt;&lt;EOF &gt; /etc/systemd/system/firewalld.service.d/99-add-allstarlink-rules.conf
 [Service]
 ExecStartPost=/usr/local/bin/nft-allow-allstarlink.sh
 EOF

 systemctl daemon-reexec
</code></pre></div>    </div>

    <p>Access the portal via <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;:3000</code>.  Again, be sure to connect to node <code class="language-plaintext highlighter-rouge">1999</code> first!  You can edit your favorite digital talkgroups by editing the <code class="language-plaintext highlighter-rouge">/opt/dvswitch_mode_switcher/configs/tg_alias.yml</code> file.  For example:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> - tgid: "yourbrandmeisterpasswordhere@3102.repeater.net:62031!91"
   alias: Brandmeister Worldwide
</code></pre></div>    </div>

    <p>Again, be sure not to use the <code class="language-plaintext highlighter-rouge">!TG</code> suffix for private calls ending in <code class="language-plaintext highlighter-rouge">#</code>.  Instead, configure and execute two commands to connect via a private call:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> - tgid: "yourbrandmeisterpasswordhere@3102.repeater.net:62031"
   alias: Brandmeister Master
 - tgid: "9990#"
   alias: Brandmeister Parrot Test (Connect to master first)
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="step-4-install-and-configure-supermon">Step 4: Install and Configure Supermon</h2>
<ol>
  <li>
    <p>Install Supermon:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /usr/local/sbin
wget <span class="s2">"http://2577.asnode.org:43856/supermonASL_fresh_install"</span> <span class="nt">-O</span> supermonASL_fresh_install
<span class="nb">chmod</span> +x supermonASL_fresh_install
./supermonASL_fresh_install
</code></pre></div>    </div>
  </li>
  <li>Configure Supermon:
    <ul>
      <li>Edit <code class="language-plaintext highlighter-rouge">allmon.ini</code> and <code class="language-plaintext highlighter-rouge">global.inc</code> in <code class="language-plaintext highlighter-rouge">/var/www/html/supermon/</code>.</li>
      <li>
        <p>Set up <code class="language-plaintext highlighter-rouge">.htpasswd</code> for authentication:</p>

        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>htpasswd <span class="nt">-cB</span> /var/www/html/supermon/.htpasswd admin
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Enable automatic database updates:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>asl3-update-astdb.service asl3-update-astdb.timer
systemctl start asl3-update-astdb.timer
</code></pre></div>    </div>
  </li>
  <li>
    <p>Access Supermon at <code class="language-plaintext highlighter-rouge">http://&lt;your_ip_address&gt;/supermon</code>.</p>
  </li>
  <li>
    <p>Update supermon with <code class="language-plaintext highlighter-rouge">/usr/local/sbin/supermonASL_latest_update</code></p>
  </li>
  <li>Edit <code class="language-plaintext highlighter-rouge">/var/www/html/supermon/almon.ini</code> to modify the top menu.  For example, you can add links to all the URLs from the tools in this document for easy access.</li>
</ol>

<h2 id="step-5-configure-skywarn-plus">Step 5: Configure Skywarn Plus</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Mason10198/SkywarnPlus/main/swp-install<span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>

<h3 id="configuration">Configuration</h3>

<ol>
  <li>
    <p>Configure <code class="language-plaintext highlighter-rouge">/usr/local/bin/SkywarnPlus/config.yaml</code> file according to readme instructions.</p>
  </li>
  <li>
    <p>Add these lines to enable weather alert tail messages at a certain interval of activity:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tailmessagetime=60000
tailsquashedtime=30000
tailmessagelist=/tmp/SkywarnPlus/wx-tail
</code></pre></div>    </div>
  </li>
  <li>
    <p>Add to root crontab:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* * * * * /usr/bin/python3 /usr/local/bin/SkywarnPlus/ASL3_Supermon_Workaround.py
* * * * * /usr/local/bin/SkywarnPlus/SkywarnPlus.py
* * * * * chown -R asterisk:asterisk /tmp/SkywarnPlus 
</code></pre></div>    </div>
  </li>
  <li>
    <p>Follow <a href="https://www.youtube.com/watch?v=lv95j-I3JDc">these instructions</a> to add time and weather hourly announcements.  Add or modify your root crontab as follows:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00 00-23 * * * /usr/bin/perl /usr/local/sbin/saytime.pl 19320 62933 &gt; /dev/null
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="allscan">Allscan</h2>
<p>Follow <a href="https://github.com/davidgsd/AllScan#readme">these instructions</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update<span class="p">;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>php unzip <span class="nt">-y</span>
<span class="nb">cd</span> ~
wget <span class="s1">'https://raw.githubusercontent.com/davidgsd/AllScan/main/AllScanInstallUpdate.php'</span>
<span class="nb">chmod </span>755 AllScanInstallUpdate.php
<span class="nb">sudo</span> ./AllScanInstallUpdate.php
</code></pre></div></div>

<p>Edit <code class="language-plaintext highlighter-rouge">/etc/php/8.2/cli/php.ini</code> and uncomment <code class="language-plaintext highlighter-rouge">extension=pdo_sqlite</code> and <code class="language-plaintext highlighter-rouge">extension=sqlite3</code>.</p>

<p>Test at <code class="language-plaintext highlighter-rouge">http://&lt;your-ip-address&gt;/allscan</code> (set up initial user).  You can now use allscan to connect/disconnect instead of allmon3 or DTMF.</p>

<p>Edit favorite nodes at <code class="language-plaintext highlighter-rouge">/var/www/html/supermon/favorites.ini</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>label[] = "Label"
cmd[] = "rpt cmd %node% ilink 3 &lt;node number&gt;"
label[] = "Echolink Label"
cmd[] = "rpt cmd %node% ilink 3 3&lt;0 prepadded 6 digit node number&gt;"
</code></pre></div></div>

<h2 id="alltune">Alltune</h2>

<p>Follow the instructions at the download provided <a href="https://www.qrz.com/db/N1ACC?aliasFrom=KQ4MZJ3">here</a>.  Extract the web files to <code class="language-plaintext highlighter-rouge">/var/www/html/alltune</code> and access at <code class="language-plaintext highlighter-rouge">http://&lt;your-ip-address&gt;/alltune</code>.</p>

<h2 id="iax-configuration">IAX Configuration</h2>

<p>You can access your AllStarNode from an Android device or other IAX connection by adding the following stanza to <code class="language-plaintext highlighter-rouge">/etc/asterisk/iax.conf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[iaxclient]                      ; Connect from iax client (Zoiper...)
type = friend                    ; Notice type here is friend &lt;--------------
context = iax-client             ; Context to jump to in extensions.conf
auth = md5
secret = your-secret-password-here
host = dynamic
disallow = all
allow = ulaw
allow = adpcm
allow = gsm
transfer = no
requirecalltoken=no ; to allow all connections
;calltokenoptional=0.0.0.0/0.0.0.0 ; to connect from a particular IP address
</code></pre></div></div>

<p>And add this in the <code class="language-plaintext highlighter-rouge">/etc/asterisk/extensions.conf</code> file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[iax-client]                            ; for IAX VoIP clients.
exten =&gt; ${NODE},1,Ringing()
        same =&gt; n,Wait(10)
        same =&gt; n,Answer()
        same =&gt; n,Set(CALLSIGN=${CALLERID(name)})
        same =&gt; n,NoOp(Caller ID name is ${CALLSIGN})
        same =&gt; n,NoOp(Caller ID number is ${CALLERID(number)})
        same =&gt; n,GotoIf(${ISNULL(${CALLSIGN})}?hangit)
        same =&gt; n,Playback(rpt/connected-to&amp;rpt/node)
        same =&gt; n,SayDigits(${NODE})
        same =&gt; n,rpt(${NODE}|P|${CALLSIGN}-P)
        same =&gt; n(hangit),NoOp(No Caller ID Name)
        same =&gt; n,Playback(connection-failed)
        same =&gt; n,Wait(1)
        same =&gt; n,Hangup
</code></pre></div></div>

<p>You can then configure DroidStar by adding the following in the <code class="language-plaintext highlighter-rouge">Hosts</code> section: <code class="language-plaintext highlighter-rouge">IAX &lt;your node number&gt; &lt;your node IP address&gt; 4569 iaxclient your-secret-password-here</code>, and choose your node under the <code class="language-plaintext highlighter-rouge">IAX</code> hosts section on the main tab.  You might have to update your databases and hosts with the buttons under <code class="language-plaintext highlighter-rouge">Settings</code> prior to use.  Once connected, you can issue DTMF codes to connect/disconnect, and use the PTT button to transmit.</p>

<h2 id="digital-link-dtmf-tuning">Digital Link DTMF Tuning</h2>

<p>To send DTMF tones to switch digital modes, servers/reflectors, and talkgroups, visit <a href="https://github.com/BillJr99/digital_link">https://github.com/BillJr99/digital_link</a> and follow the installation instructions there.</p>

<h2 id="enabling-a-thumbdv-ambe-device">Enabling a ThumbDV AMBE Device</h2>

<p>I have a USB vocoder, which I enabled by editing <code class="language-plaintext highlighter-rouge">/opt/Analog_Bridge/Analog_Bridge.ini</code> and setting:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[General]
decoderFallBack = true
userEmulator = false

[DV3000]
; address = 127.0.0.1
; rxPort = 2460
address = /dev/ttyUSB0
baud=460800
serial = true
</code></pre></div></div>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://dvswitch.org/DVSwitch_install.pdf">DVSwitch Installation Guide</a></li>
  <li><a href="http://2577.asnode.org:43856/supermonASL_fresh_install">Supermon for ASL3</a></li>
  <li><a href="https://github.com/firealarmss/dvswitch_mode_switcher">DVSwitch Mode Changer</a></li>
  <li><a href="https://github.com/VALER24/allstar-shari-dvswitch-install-guide">ASL3 Installation Guide by Ham Radio and Networking</a></li>
  <li><a href="https://www.youtube.com/watch?v=bNp-zZQKI-I">ASL3 Installation Video by Ham Radio and Networking on Youtube</a></li>
  <li><a href="https://www.youtube.com/@HamRadioCrusader">Ham Radio Crusader Youtube Channel for Information on ASL3, DVSwitch, Supermon, SkywarnPlus, and more</a></li>
  <li><a href="http://www.hamradiolife.org/documents/Supermon%20for%20ASL%203.pdf">Installing Supermon on ASL3</a></li>
  <li><a href="https://dvswitch.org/DVSwitch_install.pdf">DVSwitch Installation</a></li>
  <li><a href="https://www.youtube.com/watch?v=GRMsifz9WTg">Allmon3 and EchoLink on ASL3 by Ham Radio and Networking on Youtube</a></li>
  <li><a href="https://www.youtube.com/watch?v=uAwSHjKTeU4">DVSwitch and ClearNode on AllStar by Ham Radio and Networking on Youtube</a></li>
  <li><a href="https://www.youtube.com/watch?v=9k_gAfXJgx8">DVSwitch on AllStarLink 3 by Ham Radio and Networking on Youtube</a></li>
  <li>DVSwitch Server <a href="https://www.youtube.com/watch?v=Q73vW2tZVco">Part 1</a> and <a href="https://www.youtube.com/watch?v=HlVs9rC5pgE">Part 2</a> by Ham Radio Crusader on Youtube</li>
  <li><a href="https://allstarlink.github.io/pi/cockpit-firewall/">ASL3 Cockpit Firewall</a></li>
  <li><a href="https://www.youtube.com/watch?v=35k1sND7FbQ">SkywarnPlus on ASL3 by Ham Radio Crusader</a></li>
  <li><a href="https://www.youtube.com/watch?v=3SyCHa03pN8">Supermon 7.4+ on ASL3 by Ham Radio Crusader</a></li>
  <li><a href="http://www.hamradiolife.org/documents/Supermon%20for%20ASL%203.pdf">Supermon for ASL3</a></li>
  <li><a href="https://www.youtube.com/watch?v=lv95j-I3JDc">Time and Weather Announcements on SkywarnPlus by Ham Radio Crusader</a></li>
  <li><a href="https://github.com/davidgsd/AllScan#readme">AllScan Setup Instructions</a></li>
  <li><a href="https://www.qrz.com/db/N1ACC?aliasFrom=KQ4MZJ3">Alltune</a></li>
</ul>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="hamradio" /><summary type="html"><![CDATA[This guide walks you through installing and configuring AllStarLink (ASL3) along with several helpful management tools and utilities:]]></summary></entry><entry><title type="html">Setting Up HBLink for a Private DMR Network with HBlink</title><link href="https://www.billmongan.com/posts/2025/01/hblink/" rel="alternate" type="text/html" title="Setting Up HBLink for a Private DMR Network with HBlink" /><published>2025-01-10T00:00:00+00:00</published><updated>2025-01-10T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2025/01/hblink</id><content type="html" xml:base="https://www.billmongan.com/posts/2025/01/hblink/"><![CDATA[<p>This guide walks you through setting up HBLink for a private DMR network, including configuring a Parrot (echo test) repeater and talkgroup. Ensure that you have administrative access to your server and basic knowledge of Python.</p>

<h2 id="step-1-install-dependencies">Step 1: Install Dependencies</h2>

<ol>
  <li><strong>Create and Activate a Virtual Environment</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> venv venv
<span class="nb">source</span> ./venv/bin/activate
</code></pre></div>    </div>
  </li>
  <li><strong>Install Required Python Libraries</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./venv/bin/pip <span class="nb">install </span>dmr_utils3 dmr_utils twisted
</code></pre></div>    </div>
  </li>
  <li><strong>Clone the HBLink Repository</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/HBLink-org/hblink3 /opt/hblink/hblink3
</code></pre></div>    </div>
  </li>
  <li><strong>Navigate to the Cloned Directory</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /opt/hblink/hblink3
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="step-2-configure-hblink">Step 2: Configure HBLink</h2>

<p>Copy the <code class="language-plaintext highlighter-rouge">_SAMPLE</code> files and remove the <code class="language-plaintext highlighter-rouge">_SAMPLE</code> from the filename.  Where stock configuration items are replicated by configuration text below, that stock configuration text can be commented out with the <code class="language-plaintext highlighter-rouge">#</code> character at the beginning of each line, or removed.</p>

<h3 id="main-configuration-hblinkcfg">Main Configuration (<code class="language-plaintext highlighter-rouge">hblink.cfg</code>)</h3>

<p>The <code class="language-plaintext highlighter-rouge">hblink.cfg</code> file defines your DMR systems and how they interact. For each system:</p>
<ul>
  <li><strong>Name</strong>: Must match across configurations.</li>
  <li><strong>Port</strong>: Assign unique ports for each system.</li>
  <li><strong>Talkgroup and Timeslot Settings</strong>:
    <ul>
      <li>TG9 for local traffic on Timeslot 1.</li>
      <li>TG9999 for Parrot on Timeslot 2.</li>
    </ul>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[GLOBAL]
PATH: ./
PING_TIME: 5
MAX_MISSED: 3
USE_ACL: True
REG_ACL: PERMIT:ALL
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL

[REPORTS]
REPORT: True
REPORT_INTERVAL: 60
REPORT_PORT: 4321
REPORT_CLIENTS: 127.0.0.1

[LOGGER]
LOG_FILE: /var/log/hblink/hblink.log
LOG_HANDLERS: file-timed
LOG_LEVEL: DEBUG
LOG_NAME: HBlink

[ALIASES]
TRY_DOWNLOAD: True
PATH: ./
PEER_FILE: peer_ids.json
SUBSCRIBER_FILE: subscriber_ids.json
TGID_FILE: talkgroup_ids.json
PEER_URL: https://www.radioid.net/static/rptrs.json
SUBSCRIBER_URL: https://www.radioid.net/static/users.json
STALE_DAYS: 7

[MASTER-1]
MODE: MASTER
ENABLED: True
REPEAT: True
MAX_PEERS: 10
EXPORT_AMBE: False
IP: 
PORT: 62030
PASSPHRASE: xxx
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL

[PARROT]
MODE: PEER
ENABLED: True
LOOSE: False
EXPORT_AMBE: False
IP: 
PORT: 62031
MASTER_IP: 127.0.0.1
MASTER_PORT: 54100
PASSPHRASE: xxx
CALLSIGN: ECHO
RADIO_ID: 9999
RX_FREQ: 000000000
TX_FREQ: 000000000
TX_POWER: 1
COLORCODE: 1
SLOTS: 2
LATITUDE: 0
LONGITUDE: 0
HEIGHT: 0
LOCATION: Server Echo: TG 9999
DESCRIPTION: Echo server
URL: 
SOFTWARE_ID: DMRGateway-20190702
PACKAGE_ID: MMDVM_HBlink
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: False
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
</code></pre></div></div>

<h3 id="rules-configuration-rulespy">Rules Configuration (<code class="language-plaintext highlighter-rouge">rules.py</code>)</h3>

<p>The <code class="language-plaintext highlighter-rouge">rules.py</code> file specifies conference bridges and how systems interact. Here, I bridge TG9 over timeslots 1 and 2, so that connections can be made from either timeslot.  To force a single timeslot, comment out the timeslot under <code class="language-plaintext highlighter-rouge">PRIVATE_TG</code> that you do not wish to connect to TG9, and/or comment out the entire bridge block <code class="language-plaintext highlighter-rouge">PARROT_TS1</code> and/or <code class="language-plaintext highlighter-rouge">PARROT_TS2</code> to eliminate that timeslot.  In this example, the parrot bridges are each made on individual timeslots, so they won’t cross over.  This increases the performance of the echo replies, but prevents echos being heard on the opposite timeslot from which they arrived (this is generally expected with parrot systems).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BRIDGES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="c1"># Local/private: TG 9 on both slots. Traffic on either slot is forwarded to the other.
</span>    <span class="s">'PRIVATE_TG'</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'MASTER-1'</span><span class="p">,</span> <span class="s">'TS'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'MASTER-1'</span><span class="p">,</span> <span class="s">'TS'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
    <span class="p">],</span>

    <span class="c1"># Parrot: TG 9999 on one or both slots individually.
</span>    <span class="s">'PARROT_TS1'</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'MASTER-1'</span><span class="p">,</span> <span class="s">'TS'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9999</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9999</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'PARROT'</span><span class="p">,</span>   <span class="s">'TS'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9999</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9999</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
    <span class="p">],</span>    
    <span class="s">'PARROT_TS2'</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'MASTER-1'</span><span class="p">,</span> <span class="s">'TS'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9999</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9999</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
        <span class="p">{</span><span class="s">'SYSTEM'</span><span class="p">:</span> <span class="s">'PARROT'</span><span class="p">,</span>   <span class="s">'TS'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TGID'</span><span class="p">:</span> <span class="mi">9999</span><span class="p">,</span> <span class="s">'ACTIVE'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'TIMEOUT'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">'TO_TYPE'</span><span class="p">:</span> <span class="s">'NONE'</span><span class="p">,</span> <span class="s">'ON'</span><span class="p">:</span> <span class="p">[</span><span class="mi">9999</span><span class="p">],</span> <span class="s">'OFF'</span><span class="p">:</span> <span class="p">[]},</span>
    <span class="p">]</span>
<span class="p">}</span>

<span class="n">UNIT</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1">#['ONE', 'TWO']
</span></code></pre></div></div>

<p>Replace <code class="language-plaintext highlighter-rouge">MASTER-1</code> and <code class="language-plaintext highlighter-rouge">PARROT</code> with the names of your systems as defined in <code class="language-plaintext highlighter-rouge">hblink.cfg</code>.</p>

<h3 id="playback-configuration-playbackcfg">Playback Configuration (<code class="language-plaintext highlighter-rouge">playback.cfg</code>)</h3>

<p>Configure the Parrot repeater as described in the <a href="https://github.com/lz5pn/HBlink3/blob/main/HBlink3/playback.cfg">GitHub HBLink3 Parrot Config</a>.</p>

<ul>
  <li><strong>Talkgroup</strong>: 9999</li>
  <li><strong>Timeslot</strong>: 2</li>
  <li><strong>Color Code</strong>: 1</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[GLOBAL]
PATH: ./
PING_TIME: 10
MAX_MISSED: 5
USE_ACL: True
REG_ACL: PERMIT:ALL
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL

[REPORTS]
REPORT: False
REPORT_INTERVAL: 60
REPORT_PORT: 4322
REPORT_CLIENTS: 127.0.0.1

[LOGGER]
LOG_FILE: /var/log/hblink/parrot.log
LOG_HANDLERS: file-timed
LOG_LEVEL: INFO
LOG_NAME: Parrot

[ALIASES]
TRY_DOWNLOAD: False
PATH: ./
PEER_FILE: peer_ids.json
SUBSCRIBER_FILE: subscriber_ids.json
TGID_FILE: talkgroup_ids.json
PEER_URL: https://database.radioid.net/static/rptrs.json
SUBSCRIBER_URL: https://database.radioid.net/api/dmr/user/?country=United+States
STALE_DAYS: 7

[PARROT]
MODE: MASTER
ENABLED: True
REPEAT: True
MAX_PEERS: 10
EXPORT_AMBE: False
IP:  
PORT: 54100
PASSPHRASE: xxx
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:9999
TGID_TS2_ACL: PERMIT:9999
</code></pre></div></div>

<h3 id="testing-configuration">Testing Configuration</h3>

<p>To test the configuration, run the <code class="language-plaintext highlighter-rouge">rules.py</code> file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /var/log/hblink
python3 rules.py
</code></pre></div></div>

<h2 id="step-3-start-services">Step 3: Start Services</h2>

<p>Set up <code class="language-plaintext highlighter-rouge">/lib/systemd/system/hblink.service</code> to run <code class="language-plaintext highlighter-rouge">python bridge.py</code> (in your relevant directories) and <code class="language-plaintext highlighter-rouge">/lib/systemd/system/parrot.service</code> files to run <code class="language-plaintext highlighter-rouge">python playback.py playback.cfg</code> (again, with the relevant directory paths).  For example:</p>

<h3 id="hblinkservice"><code class="language-plaintext highlighter-rouge">hblink.service</code></h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Start HBlink
After=multi-user.target

[Service]
ExecStart=/home/w1clk/.venv/bin/python /home/w1clk/bin/hblink3/bridge.py
</code></pre></div></div>

<h3 id="parrotservice"><code class="language-plaintext highlighter-rouge">parrot.service</code></h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=HB bridge all Service
After=network-online.target syslog.target
Wants=network-online.target

[Service]
StandardOutput=null
WorkingDirectory=/home/w1clk/bin/hblink3
RestartSec=3
ExecStart=/home/w1clk/.venv/bin/python /home/w1clk/bin/hblink3/playback.py -c /home/w1clk/bin/hblink/playback.cfg
Restart=on-abort

[Install]
WantedBy=multi-user.target

systemctl enable parrot.service
systemctl enable hblink

systemctl start parrot.service
systemctl start hblink
</code></pre></div></div>

<ol>
  <li><strong>Enable and Start HBLink</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start hblink
</code></pre></div>    </div>
  </li>
  <li><strong>Enable and Start Parrot</strong>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start parrot.service
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="step-4-set-up-your-radio">Step 4: Set Up Your Radio</h2>

<ul>
  <li><strong>Talkgroups</strong>:
    <ul>
      <li>TG9 for local communication.</li>
      <li>TG9999 for Parrot playback.</li>
    </ul>
  </li>
  <li><strong>Timeslots</strong>:
    <ul>
      <li>TG9 on Timeslot 1.</li>
      <li>TG9999 on Timeslot 2.</li>
    </ul>
  </li>
  <li><strong>Color Code</strong>: 1.</li>
  <li><strong>Monitor Mode</strong>: Enable monitor mode or add the talkgroups to a receive group to hear responses.</li>
</ul>

<h3 id="talkgroup-forawrding-with-a-pi-star">Talkgroup Forawrding with a pi-star</h3>

<p>If you are using a pi-star or wpsd device for DMR, you can configure an additional DMR master network with talkgroup forwarding, which will enable you to program unique talkgroup numbers into your radio for use with the pi-star, and the pi-star will forward those talkgroup numbers to the ones we configured on the HBlink server.  Add a DMR network with the following settings, or add the following stanza to the DMR configuration file (using the passphrase you chose in the configuration files):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[DMR Network 4]
Enabled=1
Name=HBlink
Id=&lt;your DMR ID here&gt;
Address=&lt;IP address of your HBlink server&gt;
Port=62030
TGRewrite0=1,60009,1,9,1
TGRewrite1=1,69999,2,9999,1 
TGRewrite2=2,60009,1,9,1 
TGRewrite3=2,69999,2,9999,1
Password="xxx"
Location=0
Debug=0
</code></pre></div></div>

<p>Substitue “DMR Network #” for the appropriate network number - generally one higher than the other stanzas.  This rewrites talkgroups 60009 to HBlink talkgroup 9, and 69999 to HBlink talkgroup 9999.  You can adjust 60009 and 69999 to whatever you’d like to use on your radio, and adjust 9 and 9999 to the ones you configured when you set up the HBlink server configuration files above.</p>

<h2 id="notes">Notes</h2>

<p>I set the password in this guide to <code class="language-plaintext highlighter-rouge">xxx</code>, but you can replace this with a password of your choosing.  Be sure to replace it in all config files!  In addition, the primary port for the HBlink server in this configuration is 62030, while the parrot repeater uses ports 62031 and 54001 behind the scenes.  These can be adjusted in the configuration files as well.</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://github.com/HBLink-org/hblink3">HBLink GitHub Repository</a></li>
  <li><a href="https://dvswitch.groups.io/g/HBlink/topic/newbie_setting_up_private_dmr/75273923">DMR Configuration Guide</a></li>
  <li><a href="https://github.com/lz5pn/HBlink3">HBlink self-contained forked repository and configuration details</a></li>
</ul>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="hamradio" /><summary type="html"><![CDATA[This guide walks you through setting up HBLink for a private DMR network, including configuring a Parrot (echo test) repeater and talkgroup. Ensure that you have administrative access to your server and basic knowledge of Python.]]></summary></entry><entry><title type="html">Setting Up FreePBX on a Raspberry Pi with AREDN MeshPhone and 44net Integration</title><link href="https://www.billmongan.com/posts/2025/01/pbx/" rel="alternate" type="text/html" title="Setting Up FreePBX on a Raspberry Pi with AREDN MeshPhone and 44net Integration" /><published>2025-01-10T00:00:00+00:00</published><updated>2025-01-10T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2025/01/pbx</id><content type="html" xml:base="https://www.billmongan.com/posts/2025/01/pbx/"><![CDATA[<p>This guide will walk you through setting up FreePBX on a Raspberry Pi, trunking it with MeshPhone over an AREDN network, and optionally making it accessible via a 44net allocation.</p>

<h2 id="prerequisites">Prerequisites</h2>
<ul>
  <li>A Raspberry Pi with a supported operating system.</li>
  <li>An AREDN node with MeshPhone support.</li>
  <li>A 44net allocation (optional).</li>
</ul>

<h2 id="step-1-install-freepbx">Step 1: Install FreePBX</h2>
<!--Follow the detailed installation guide for FreePBX on a Raspberry Pi available at:
[DSLReports FreePBX Installation Guide](https://www.dslreports.com/forum/r30661088-PBX-FreePBX-for-the-Raspberry-Pi)-->

<p>I installed FreePBX version 17.0.19.17 with Asterisk 21.5.0 using a script.  You can also flash a prebuilt FreePBX Raspberry Pi image to your SD card.</p>

<h2 id="step-2-configure-freepbx">Step 2: Configure FreePBX</h2>
<p>Use the configuration instructions provided on this mesh page (AREDN mesh connection required to view):
<a href="http://n2mh-meshphone2.local.mesh/meshphone/freepbx/MPFreePBXHowTo_v2.html">MeshPhone FreePBX Configuration</a></p>

<h3 id="configuration-steps">Configuration Steps</h3>
<ol>
  <li><strong>Create an Extension:</strong>
    <ul>
      <li>Note the <code class="language-plaintext highlighter-rouge">secret</code>, which will be provisioned on your phone.</li>
      <li>Configure your phone with the server IP and port (default: 5060).</li>
    </ul>
  </li>
  <li><strong>Firewall Configuration:</strong>
If using 44net, set up your router and FreePBX node firewall.</li>
</ol>

<h2 id="step-3-router-and-firewall-configuration">Step 3: Router and Firewall Configuration</h2>

<h3 id="on-the-44net-router">On the 44net Router:</h3>
<p>If your AREDN router is behind a 44net router, forward traffic for SIP (port 5060) as follows:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip firewall filter add <span class="nv">chain</span><span class="o">=</span>input <span class="nv">protocol</span><span class="o">=</span>tcp <span class="nv">port</span><span class="o">=</span>5060 src-address<span class="o">=</span>&lt;your 44net cloud IP&gt; <span class="nv">action</span><span class="o">=</span>accept <span class="nv">comment</span><span class="o">=</span><span class="s2">"Allow TCP 5060 from specific IP"</span>
/ip firewall filter add <span class="nv">chain</span><span class="o">=</span>input <span class="nv">protocol</span><span class="o">=</span>udp <span class="nv">port</span><span class="o">=</span>5060 src-address<span class="o">=</span>&lt;your 44net cloud IP&gt; <span class="nv">action</span><span class="o">=</span>accept <span class="nv">comment</span><span class="o">=</span><span class="s2">"Allow UDP 5060 from specific IP"</span>
</code></pre></div></div>
<p>Move these rules above any default drop rules in WebFig.</p>

<h3 id="on-the-aredn-node">On the AREDN Node:</h3>
<p>Forward port 5060 (TCP/UDP) to the FreePBX server.</p>

<h3 id="on-the-freepbx-node">On the FreePBX Node:</h3>
<p>If using 44net, add your allocation to SIP Settings as a local network:</p>
<ul>
  <li><strong>Under SIP Settings:</strong> Add your 44net allocation.</li>
</ul>

<p>Add firewall rules via SSH to allow traffic:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>iptables <span class="nt">-A</span> INPUT <span class="nt">-p</span> udp <span class="nt">--dport</span> 5060 <span class="nt">-s</span> &lt;your AMPR 44net allocation&gt;/&lt;subnet&gt; <span class="nt">-j</span> ACCEPT
<span class="nb">sudo </span>iptables <span class="nt">-A</span> INPUT <span class="nt">-p</span> tcp <span class="nt">--dport</span> 5060 <span class="nt">-s</span> &lt;your AMPR 44net allocation&gt;/&lt;subnet&gt; <span class="nt">-j</span> ACCEPT
<span class="nb">sudo </span>iptables <span class="nt">-A</span> INPUT <span class="nt">-p</span> tcp <span class="nt">--dport</span> 5060 <span class="nt">-s</span> &lt;your 44net cloud IP&gt;/32 <span class="nt">-j</span> ACCEPT
<span class="nb">sudo </span>iptables <span class="nt">-A</span> INPUT <span class="nt">-p</span> udp <span class="nt">--dport</span> 5060 <span class="nt">-s</span> &lt;your 44net cloud IP&gt;/32 <span class="nt">-j</span> ACCEPT
</code></pre></div></div>
<p>Make these rules persistent:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>iptables-save <span class="o">&gt;</span> /etc/iptables/rules.v4
</code></pre></div></div>

<h2 id="step-4-trunk-configuration">Step 4: Trunk Configuration</h2>
<p>Modify the following files to set up trunking with MeshPhone. Retrieve example files from:
<a href="http://n2mh-meshphone2.local.mesh/meshphone/freepbx/MPFreePBXHowTo_v2.html">MeshPhone FreePBX Configuration</a></p>

<h3 id="file-modifications">File Modifications</h3>
<h4 id="iax_customconf"><code class="language-plaintext highlighter-rouge">iax_custom.conf</code></h4>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[&lt;your peer's callsign here&gt;]</span>    <span class="c">; must match username coming from far end
</span><span class="py">type</span><span class="p">=</span><span class="s">friend                      ; peer=outgoing; user=incoming; friend=both way</span>
<span class="py">host</span><span class="p">=</span><span class="s">&lt;your peer's PBX IP address here&gt;  ; send calls to this host IP address at far end</span>
<span class="py">username</span><span class="p">=</span><span class="s">&lt;your callsign here&gt;   ; username sent to far end</span>
<span class="py">secret</span><span class="p">=</span><span class="s">&lt;a shared password here&gt; ; shared secret between both ends</span>
<span class="py">auth</span><span class="p">=</span><span class="s">plaintext                   ; plaintext; rsa; md5</span>
<span class="py">context</span><span class="p">=</span><span class="s">MeshPhone                ; destination context for incoming calls</span>
<span class="py">disallow</span><span class="p">=</span><span class="s">all                     ; disallow all incoming codecs</span>
<span class="py">allow</span><span class="p">=</span><span class="s">ulaw                       ; allow ulaw codec</span>
<span class="py">allow</span><span class="p">=</span><span class="s">alaw                       ; allow alaw codec</span>
<span class="py">allow</span><span class="p">=</span><span class="s">gsm                        ; allow gsm codec</span>
<span class="py">qualify</span><span class="p">=</span><span class="s">yes                      ; constantly ping far end to determine trunk status</span>
<span class="py">transfer</span><span class="p">=</span><span class="s">no                      ; do not directly connect endpoints</span>
<span class="py">trunk</span><span class="p">=</span><span class="s">yes                        ; allow multiple simultaneous calls</span>
</code></pre></div></div>

<h4 id="globals_customconf"><code class="language-plaintext highlighter-rouge">globals_custom.conf</code></h4>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">NEIGHBOR</span><span class="p">=</span><span class="s">&lt;your peer's callsign here&gt;</span>
<span class="c">; [globals] line commented out (if necessary)
</span></code></pre></div></div>

<h4 id="meshphoneconf"><code class="language-plaintext highlighter-rouge">meshphone.conf</code></h4>
<p>Add below the first local extension rule, if not already present. Replace <code class="language-plaintext highlighter-rouge">XXXXXXX</code> with the peer’s phone number range (e.g., <code class="language-plaintext highlighter-rouge">55512xx</code> for numbers starting with <code class="language-plaintext highlighter-rouge">55512</code>):</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">; NEIGHBOR=&lt;your peer's callsign here&gt;
</span><span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _XXXXXXX,1,Dial(IAX2/${NEIGHBOR}/${EXTEN})</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _XXXXXXX,n,GoTo(Utilities,Sorry,1)</span>
</code></pre></div></div>

<h4 id="extensions_customconf"><code class="language-plaintext highlighter-rouge">extensions_custom.conf</code></h4>
<p>Add below access codes to create 4-digit dialing rules. Replace <code class="language-plaintext highlighter-rouge">XXXX</code> with the dialing plan and <code class="language-plaintext highlighter-rouge">YYY</code> with the area code to prepend:</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _XXXX,1,GoTo(MeshPhone,YYY${EXTEN},1)</span>
</code></pre></div></div>

<p>And to get the office code to echo by overriding the default error response, add this to the bottom of <code class="language-plaintext highlighter-rouge">extensions_custom.conf</code>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[ext-local-custom]</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,1,NoOp(Custom Bad-Number Handling)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,ResetCDR()</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,Set(CDR_PROP(disable)=true)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,Progress()</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,Wait(1)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,Goto(Utilities,Sorry,1) ; Redirect to custom "Sorry" message</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; _X.,n,Hangup()</span>

<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,1,NoOp(Custom Invalid Number Handling)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,ResetCDR()</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,Set(CDR_PROP(disable)=true)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,Progress()</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,Wait(1)</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,Goto(Utilities,Sorry,1) ; Redirect to custom "Sorry" message</span>
<span class="py">exten</span> <span class="p">=</span><span class="s">&gt; i,n,Hangup()</span>
</code></pre></div></div>

<h2 id="optional-public-availability-via-44net">Optional: Public Availability via 44net</h2>
<p>To connect a phone via Groundwire using your 44net allocation:</p>
<ol>
  <li>Set up a WireGuard connection from your mobile phone to the 44net allocation.</li>
  <li>Configure Groundwire to connect to your PBX over the 44net address (making sure to allow that 44net address through your 44net firewall, your PBX firewall, and port forwarding from the AREDN router).</li>
</ol>

<h3 id="installing-wireguard">Installing WireGuard</h3>

<p>To install Wireguard (if needed to make a connection to 44net), run the following from your PBX:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install wireguard -y
sudo modprobe wireguard resolvconf
</code></pre></div></div>

<h3 id="configure-wireguard">Configure WireGuard</h3>

<p>Then, save your public and private key to <code class="language-plaintext highlighter-rouge">/etc/wireguard/publickey</code> and <code class="language-plaintext highlighter-rouge">/etc/wireguard/privatekey</code>, respectively, and enter your Wireguard configuration and peer information into <code class="language-plaintext highlighter-rouge">/etc/wireguard/wg0.conf</code>.  Set their permissions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod 600 /etc/wireguard/privatekey
chmod 600 /etc/wireguard/wg0.conf
</code></pre></div></div>

<p>I removed the IPv6 address from my Wireguard configuration because my pbx does not support IPv6.</p>

<h3 id="starting-the-wireguard-tunnel">Starting the Wireguard Tunnel</h3>

<p>Start the tunnel to test, and set it to start at startup, via:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
</code></pre></div></div>

<h3 id="allow-sip-ports-through-the-wireguard-interface">Allow SIP Port(s) through the Wireguard Interface</h3>

<p>Modify iptables as follows to allow port 5060 (and/or others) through the <code class="language-plaintext highlighter-rouge">wg0</code> interface:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo iptables -A INPUT -i wg0 -p udp --dport 5060 -j ACCEPT
sudo iptables -A INPUT -i wg0 -p tcp --dport 5060 -j ACCEPT
sudo cp /etc/iptables/rules.v4 /etc/iptables/rules.v4.bak
sudo iptables-save &gt;/etc/iptables/rules.v4
</code></pre></div></div>

<h3 id="set-the-outside-ip-address-and-local-networks-on-the-freepbx-configuration-portal">Set the Outside IP Address and Local Networks on the FreePBX Configuration Portal</h3>

<p>Log into the FreePBX webpage, and go to <code class="language-plaintext highlighter-rouge">Settings</code> - <code class="language-plaintext highlighter-rouge">Asterisk SIP Settings</code>.  Set your external address to your tunnel address (or other outside address), and add the subnet of that address to your list of local networks.</p>

<h3 id="enabling-multiple-registrations-per-line">Enabling Multiple Registrations Per line</h3>

<p>To allow pjsip extension users to have multiple registrations (i.e., phones) on their line, you can go to <code class="language-plaintext highlighter-rouge">Connectivity</code> - <code class="language-plaintext highlighter-rouge">Extensions</code> - <code class="language-plaintext highlighter-rouge">Advanced</code> and set <code class="language-plaintext highlighter-rouge">Max Connections</code> to <code class="language-plaintext highlighter-rouge">5</code>.  You can also set <code class="language-plaintext highlighter-rouge">Outbound Concurrency Limit</code> to <code class="language-plaintext highlighter-rouge">3</code>.  Then, under <code class="language-plaintext highlighter-rouge">Connectivity</code> - <code class="language-plaintext highlighter-rouge">Extensions</code> - <code class="language-plaintext highlighter-rouge">Find Me/Follow Me</code>, enable this feature and set the rule to <code class="language-plaintext highlighter-rouge">ringallv2-prim</code>.</p>

<h2 id="optional-fail2ban">Optional: fail2ban</h2>

<p>To auto-reject invalid registration attempts, you can install fail2ban via:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install fail2ban
</code></pre></div></div>

<hr />

<p>Save and apply all changes to FreePBX and your network configurations.</p>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="hamradio" /><summary type="html"><![CDATA[This guide will walk you through setting up FreePBX on a Raspberry Pi, trunking it with MeshPhone over an AREDN network, and optionally making it accessible via a 44net allocation.]]></summary></entry><entry><title type="html">Controlling Last-Mile Logistics Reduces Failures and Waste</title><link href="https://www.billmongan.com/posts/2023/04/logistics/" rel="alternate" type="text/html" title="Controlling Last-Mile Logistics Reduces Failures and Waste" /><published>2023-04-11T00:00:00+00:00</published><updated>2023-04-11T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2023/04/logistics</id><content type="html" xml:base="https://www.billmongan.com/posts/2023/04/logistics/"><![CDATA[<p>Making sure your products reach their final destination has become more difficult in recent years. With a few “life hacks,” I’ve taken control and reduced wasteful failures in the system both at home and at work. You can, too!  Here is an <a href="https://www.linkedin.com/pulse/controlling-last-mile-logistics-reduces-failures-bill-mongan-ph-d-">article I wrote about some of the ways I control my shipping and receiving operations</a>.</p>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="logistics" /><summary type="html"><![CDATA[Making sure your products reach their final destination has become more difficult in recent years. With a few “life hacks,” I’ve taken control and reduced wasteful failures in the system both at home and at work. You can, too! Here is an article I wrote about some of the ways I control my shipping and receiving operations.]]></summary></entry><entry><title type="html">Using ChatGPT to Write Code</title><link href="https://www.billmongan.com/posts/2023/03/chatgptunittests/" rel="alternate" type="text/html" title="Using ChatGPT to Write Code" /><published>2023-03-14T00:00:00+00:00</published><updated>2023-03-14T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2023/03/chatgptunittests</id><content type="html" xml:base="https://www.billmongan.com/posts/2023/03/chatgptunittests/"><![CDATA[<p>I’ve been asked occasionally about my opinion about generative AI tools such as Chat GPT and their potential to disrupt the way we design and create.  While I think there is a risk that fundamental knowledge and skill may erode as they are abstracted away by tools like these, I also think these tools create wonderful “jumping off points” for prototyping ideas.  We still need a technically educated population to a) know what questions to ask, b) ask them in a precise way, and c) validate the results.</p>

<p>So I propose that we acknowledge these as guidance systems, and provide some mentorship and coaching regarding the risks in using them, and ways to mitigate that risk.  In particular, I see an opportunity to motivate and to teach software testing as even more critical as we rely more heavily on automatically generated code (or, for that matter, code that one finds from anonymous sources on the Internet!).</p>

<p>To try this out, I provided the following prompt to ChatGPT:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Write a python program that extracts N bits from an IPv4 address given a netmask N
</code></pre></div></div>

<p>And I got the following response:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">extract_bits</span><span class="p">(</span><span class="n">ip_address</span><span class="p">,</span> <span class="n">netmask_bits</span><span class="p">):</span>
    <span class="c1"># Convert IP address to a list of integers
</span>    <span class="n">ip_list</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">ip_address</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"."</span><span class="p">)]</span>

    <span class="c1"># Create a binary string of the netmask
</span>    <span class="n">netmask</span> <span class="o">=</span> <span class="s">"1"</span> <span class="o">*</span> <span class="n">netmask_bits</span> <span class="o">+</span> <span class="s">"0"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">32</span> <span class="o">-</span> <span class="n">netmask_bits</span><span class="p">)</span>

    <span class="c1"># Convert the binary netmask to a list of integers
</span>    <span class="n">netmask_list</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">netmask</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">8</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">8</span><span class="p">)]</span>

    <span class="c1"># Apply the netmask to the IP address
</span>    <span class="n">masked_ip</span> <span class="o">=</span> <span class="p">[</span><span class="n">ip_list</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&amp;</span> <span class="n">netmask_list</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span>

    <span class="c1"># Convert the masked IP address to a binary string
</span>    <span class="n">masked_ip_str</span> <span class="o">=</span> <span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">format</span><span class="p">(</span><span class="n">byte</span><span class="p">,</span> <span class="s">"08b"</span><span class="p">)</span> <span class="k">for</span> <span class="n">byte</span> <span class="ow">in</span> <span class="n">masked_ip</span><span class="p">)</span>

    <span class="c1"># Extract the first N bits from the masked IP address
</span>    <span class="n">extracted_bits</span> <span class="o">=</span> <span class="n">masked_ip_str</span><span class="p">[:</span><span class="n">netmask_bits</span><span class="p">]</span>

    <span class="c1"># Convert the extracted bits to an integer
</span>    <span class="n">extracted_int</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">extracted_bits</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">extracted_int</span>
</code></pre></div></div>

<p>It even provided me with an example on how to invoke this function:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># In this example, the function extracts the first 24 bits (corresponding to the first three octets) from the IP address "192.168.1.100" using a netmask of 24 bits. The resulting value is 192.
</span>
<span class="n">ip_address</span> <span class="o">=</span> <span class="s">"192.168.1.100"</span>
<span class="n">netmask_bits</span> <span class="o">=</span> <span class="mi">24</span>
<span class="n">extracted_bits</span> <span class="o">=</span> <span class="n">extract_bits</span><span class="p">(</span><span class="n">ip_address</span><span class="p">,</span> <span class="n">netmask_bits</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">extracted_bits</span><span class="p">)</span>  <span class="c1"># Output: 192
</span></code></pre></div></div>

<p>Pretty impressive, even if it is simply mining the Internet for text to synthesize as a response.  The problem is that it is not clear that this program is generating the correct output.  When I call this function according to the example it gave, I get <code class="language-plaintext highlighter-rouge">12625921</code> as the output.  Incidentally, that’s <code class="language-plaintext highlighter-rouge">0b0b110000001010100000000001</code> in binary.  That does correspond to the first 24 bits of the address.  But the comment says to expect the output to be 192, which is not quite right.  Even still, it would be nice to report the network address in the typical <code class="language-plaintext highlighter-rouge">a.b.c.d</code> format, or at least as a list of octets, such as <code class="language-plaintext highlighter-rouge">[192, 168, 1, 0]</code>.  Perhaps this was the result of ambiguity in the question I posed to ChatGPT, but regardless, I’m curious as to whether we could continue to use ChatGPT prompts to finish this program.</p>

<p>I decided to ask ChatGPT to write another program to help me to do this, by converting a 32-bit integer (that the original function was returning to me) into a list of 4 octets, like a typical IPv4 network address:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Write a program to convert an integer containing 32 bits to a list of four 8-bit values in python
</code></pre></div></div>

<p>To which it wrote:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">int_to_bytes</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="c1"># Convert integer to binary string
</span>    <span class="n">binary_str</span> <span class="o">=</span> <span class="nb">format</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="s">"032b"</span><span class="p">)</span>

    <span class="c1"># Split binary string into four 8-bit chunks
</span>    <span class="n">byte_strs</span> <span class="o">=</span> <span class="p">[</span><span class="n">binary_str</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">8</span><span class="p">)]</span>

    <span class="c1"># Convert each 8-bit chunk to an integer
</span>    <span class="nb">bytes</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">byte_str</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">byte_str</span> <span class="ow">in</span> <span class="n">byte_strs</span><span class="p">]</span>

    <span class="k">return</span> <span class="nb">bytes</span>
</code></pre></div></div>

<p>It provided the following example invocation:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># In this example, the function converts the integer 4278190080 to a binary string ("11111111000000000000000011111111") and then splits it into four 8-bit chunks ("11111111", "00000000", "00000000", and "11111111"). Finally, it converts each 8-bit chunk to an integer ([255, 0, 0, 255]). The resulting value is a list of four 8-bit values that represent the number 4278190080 in binary.
</span>
<span class="n">n</span> <span class="o">=</span> <span class="mi">4278190080</span>
<span class="nb">bytes</span> <span class="o">=</span> <span class="n">int_to_bytes</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="nb">bytes</span><span class="p">)</span>  <span class="c1"># Output: [255, 0, 0, 255]
</span></code></pre></div></div>

<p>Interesting, it got the comment wrong in the example again.  <code class="language-plaintext highlighter-rouge">[255, 0, 0, 0]</code> is the expected output for this example.</p>

<p>To combine these two programs, I called <code class="language-plaintext highlighter-rouge">int_to_bytes</code> from the original <code class="language-plaintext highlighter-rouge">extract_bits</code> function, right before the return statement.  To do this, I had to shift the network address to the left, since the network address bits are the uppermost bits of an IPv4 address.  Then, I called <code class="language-plaintext highlighter-rouge">int_to_bytes</code> with this resulting value.  Although I did this manually, I think it would be an interesting assignment to ask students to continue to prompt ChatGPT until it came up with this solution, sort of like a coding version of the game “Taboo.”  Here’s the solution, now ask the right question to get an AI to generate it.  It’s a little more challenging that it sounds.  For example, one wouldn’t know to ask the AI to shift the bits of the network address without an understanding of IP address formats.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1"># PAD 0's ON THE LEAST SIGNIFICANT (COMPUTER ADDRESS) BITS
</span>    <span class="n">extracted_int</span> <span class="o">=</span> <span class="n">extracted_int</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="mi">32</span> <span class="o">-</span> <span class="n">netmask_bits</span><span class="p">)</span>
        
    <span class="c1"># CONVERT BACK TO OCTETS
</span>    <span class="n">extracted_int</span> <span class="o">=</span> <span class="n">int_to_bytes</span><span class="p">(</span><span class="n">extracted_int</span><span class="p">)</span>
</code></pre></div></div>

<p>Additionally, we should take care to validate this program.  We always say this is a best practice, but students are usually pretty confident in their solutions.  Perhaps they’d be more convinced that some arbitrary code shouldn’t be trusted, especially if the output does not make sense, and if it does not align with the generated comment.  Another fun exercise is to generate unit tests for AI generated code, and to revise the prompts until the tests pass.  Using <code class="language-plaintext highlighter-rouge">pytest</code>, here are a few brief tests for the code that my prompts generated here:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># pip install pytest
# run with: pytest
</span>
<span class="kn">from</span> <span class="nn">to_octets</span> <span class="kn">import</span> <span class="n">int_to_bytes</span> 
<span class="kn">from</span> <span class="nn">netmask2</span> <span class="kn">import</span> <span class="n">extract_bits</span> 

<span class="k">def</span> <span class="nf">test_netmask</span><span class="p">():</span>
    <span class="k">assert</span> <span class="n">extract_bits</span><span class="p">(</span><span class="s">"192.168.1.100"</span><span class="p">,</span> <span class="mi">24</span><span class="p">)</span> <span class="o">==</span> <span class="p">[</span><span class="mi">192</span><span class="p">,</span> <span class="mi">168</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">test_int_to_bytes</span><span class="p">():</span>
    <span class="k">assert</span> <span class="n">int_to_bytes</span><span class="p">(</span><span class="mi">4278190080</span><span class="p">)</span> <span class="o">==</span> <span class="p">[</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<p>Impressively, ChatGPT got this program nearly correct on the first prompt.  However, it took a little massaging to get things quite right.  This is arguably due to unclear wording in the question itself, but that’s the point: how would you know?  Beyond this, the commenting of the code itself wasn’t quite right, which doesn’t inspire confidence!  I think this leads to a fun game, of sorts, in which you propose a problem statement, ask an AI to generate a solution, test it, and refine the prompt.  There’s a lot to learn about how these generative AI systems work, about how we can leverage them in healthy ways, and, perhaps most importantly, about the limitations of these systems and how the “human-in-the-loop” is expected to contribute to the solution.</p>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="education" /><category term="technical" /><summary type="html"><![CDATA[I’ve been asked occasionally about my opinion about generative AI tools such as Chat GPT and their potential to disrupt the way we design and create. While I think there is a risk that fundamental knowledge and skill may erode as they are abstracted away by tools like these, I also think these tools create wonderful “jumping off points” for prototyping ideas. We still need a technically educated population to a) know what questions to ask, b) ask them in a precise way, and c) validate the results.]]></summary></entry><entry><title type="html">Integrating the micro:bit with VarIOT via ThingsBoard</title><link href="https://www.billmongan.com/posts/2022/07/variotmicrobit/" rel="alternate" type="text/html" title="Integrating the micro:bit with VarIOT via ThingsBoard" /><published>2022-07-04T00:00:00+00:00</published><updated>2022-07-04T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2022/07/variotmicrobit</id><content type="html" xml:base="https://www.billmongan.com/posts/2022/07/variotmicrobit/"><![CDATA[<p>In this article, we will integrate the <a href="https://microbit.org/">BBC micro:bit</a> with a <a href="https://thingsboard.io/">ThingsBoard</a> gateway called <a href="https://drexel.edu/engineering/news-events/news/archive/2021/December/vip-project-helps-student-make-connections/">VarIOT</a>.</p>

<p>If we have a VarIOT ThingsBoard gateway (which can be installed on a <a href="https://www.raspberrypi.org/">Raspberry Pi</a>), we can connect it with a ThingsBoard server (one of which is hosted at Drexel Univesrity) to send telemetry data from IoT sensors that we can collect and evaluate using a micro:bit.  For example, we could send environmental data like temperature or humidity, or biomedical data like one’s respiratory rate (or anything else you can imagine!).</p>

<h2 id="configuring-a-variot-gateway">Configuring a VarIOT Gateway</h2>

<p>You can install VarIOT and the ThingsBoard software on a Raspberry Pi by following <a href="https://github.com/drexelwireless/thingsboard-gateway/blob/master/docs/installation/tb-gateway.rst">these instructions</a>.  These steps also describe how to connect a gateway to a ThingsBoard server.</p>

<h3 id="enabling-web-access-to-the-gateway">Enabling Web Access to the Gateway</h3>

<p>I edited the <code class="language-plaintext highlighter-rouge">~/thingsboard-gateway/thingsboard-gateway/thingsboard_gateway/config/tb-gateway.yaml</code> configuration file on my Raspberry Pi and un-commented the code in the REST block to allow REST access from the micro:bit.</p>

<p>ThingsBoard provides <a href="https://thingsboard.io/docs/iot-gateway/getting-started/">an article</a> describing how to configure the gateway and, specifically, how to <a href="https://thingsboard.io/docs/iot-gateway/config/rest/">configure RESTful access</a>.  You can add your own endpoints here following the template provided in the <code class="language-plaintext highlighter-rouge">~/thingsboard-gateway/thingsboard-gateway/thingsboard_gateway/config/rest.json</code> file.</p>

<p>One thing that is required is to change the host name from <code class="language-plaintext highlighter-rouge">127.0.0.1</code> to <code class="language-plaintext highlighter-rouge">0.0.0.0</code> so that the endpoints are available from the outside world.  I did not enable SSL, since I don’t have SSL built into the code of the HTTP extension driver.  For this reason, I’m also not using a username or password, since these would be sent in clear text, but these are prototyping practices only (and not for production use!).</p>

<h3 id="connecting-to-a-vpn">Connecting to a VPN</h3>
<p>If needed, you can VPN to the ThingsBoard server by running: <code class="language-plaintext highlighter-rouge">sudo openconnect -u &lt;your user name&gt; -b &lt;vpn server name&gt;</code> on your Gateway Raspberry Pi.  This will allow your gateway to connect to a server that is behind a VPN.</p>

<h3 id="testing-the-restful-configuration">Testing the RESTful Configuration</h3>
<p>In my <code class="language-plaintext highlighter-rouge">rest.json</code> file, I see that I have an endpoint called <code class="language-plaintext highlighter-rouge">my_devices</code>.  This is not the most descriptive name, since it’s just a template, but rather than modify it, I’ll use it for this demo so that we have a proof of concept with minimal configuration effort.  We can test this endpoint using a curl command from a terminal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://rpi4-variot:5000/my_devices -H "Content-Type: application/json" -X POST --data '{"temp": 25, "sensorName": "Mongan Gateway", "sensorType": "default", "sensorModel": "testmodel"}'
</code></pre></div></div>

<p>This endpoint is configured to map the <code class="language-plaintext highlighter-rouge">temp</code> parameter to the <code class="language-plaintext highlighter-rouge">temperature</code> telemetry value.  For fun, I also send an attribute called <code class="language-plaintext highlighter-rouge">model</code> that is mapped to the <code class="language-plaintext highlighter-rouge">sensorModel</code> parameter in the configuration, and I will send the value <code class="language-plaintext highlighter-rouge">testmodel</code>.  This can be left out.  When I configured my Gateway, I called my gateway sensor <code class="language-plaintext highlighter-rouge">Mongan Gateway</code>, and so I will configure this as the <code class="language-plaintext highlighter-rouge">sensorName</code> as well.</p>

<h2 id="connecting-the-microbit-to-wifi">Connecting the micro:bit to WiFi</h2>

<p>Next, we will connect the micro:bit to WiFi so that we can connect to the Internet and send telemetry data to the gateway.  The instructions and diagrams provided by <a href="https://github.com/alankrantas/pxt-ESP8266_ThingSpeak">these instructions</a> for a ThingSpeak board are exactly what we need.  I did not bother connecting an LED or the USB, so only the connections to the ESP8266 WiFi module was needed.</p>

<p>I used a breadboard, jumper wires, and an ESP8266 WiFi Module to do this, as well as a breadboard power supply.  I also used a micro:bit breakout board so that I can connect jumper cables to the pins of the micro:bit from the breadboard, rather than needing to use the allegator clips to clamp to the metal pads.  I’ll include links to the items I used.</p>

<ul>
  <li><a href="https://smile.amazon.com/dp/B08JYPMCZY?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details">MB-102 Breadboard Power Supply</a></li>
  <li><a href="https://smile.amazon.com/dp/B08DFWQBMB?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details">MB-102 Breadboard</a></li>
  <li><a href="https://smile.amazon.com/dp/B07GD2BWPY?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details">Jumper Cables</a></li>
  <li><a href="https://smile.amazon.com/Edge-Connector-Breakout-Board-Micro/dp/B082Z6H5LB/ref=sr_1_4?crid=994DJZFDCISO&amp;keywords=microbit+breakout+board&amp;qid=1656939978&amp;s=industrial&amp;sprefix=microbit+breakout+board%2Cindustrial%2C51&amp;sr=1-4">Microbit Breakout Board</a></li>
</ul>

<p>I connected the Rx pin of the WiFi module to P0 of the micro:bit, the Tx pin to P1, VCC and CH_EN to the 3V pin for power, and ground to the ground pin, as shown in the diagrams of the wiring diagram and pinout (shown below; image credit Alan Krantas via the <a href="https://github.com/alankrantas/pxt-ESP8266_ThingSpeak">alankrantis/pxt-ESP8266_ThingSpeak</a> GitHub repository under an <a href="https://raw.githubusercontent.com/alankrantas/pxt-ESP8266_ThingSpeak/master/LICENSE">MIT License</a>):</p>

<p><img src="https://user-images.githubusercontent.com/44191076/57862847-9c235980-782b-11e9-9588-3e7fe76342ee.png" alt="Wiring diagram - Alan Krantas GitHub under an MIT License" /></p>

<p><img src="https://user-images.githubusercontent.com/44191076/50428909-fc097a00-08f5-11e9-91f1-921d1b957f29.png" alt="Pinout - Alan Krantas GitHub under an MIT License" /></p>

<p>Following these diagrams, I wired my WiFi module to the micro:bit as follows:</p>

<div style="text-align:center;">
<img src="/files/media/2022-07-04-variotmicrobit/fig1.jpg" alt="Finished WiFi to micro:bit breadboard wiring figure 1" />
<img src="/files/media/2022-07-04-variotmicrobit/fig2.jpg" alt="Finished WiFi to micro:bit breadboard wiring figure 2" />
<img src="/files/media/2022-07-04-variotmicrobit/fig3.jpg" alt="Finished WiFi to micro:bit breadboard wiring figure 3" />
<img src="/files/media/2022-07-04-variotmicrobit/fig4.jpg" alt="Finished WiFi to micro:bit breadboard wiring figure 4" />
<img src="/files/media/2022-07-04-variotmicrobit/fig5.jpg" alt="Finished WiFi to micro:bit breadboard wiring figure 5" />
</div>

<h2 id="writing-the-microbit-program">Writing the micro:bit Program</h2>
<p>Finally, we can import this extension into a micro:bit project.  To do this, create a micro:bit project at <a href="https://makecode.microbit.org">makecode.microbit.org</a> and click the settings cog at the top right.  Choose <code class="language-plaintext highlighter-rouge">Extensions</code>, and search for VarIOT.  Alternatively, you can import this URL directly to import the extension from GitHub: <a href="https://github.com/BillJr99/pxt-ESP8266_VarIOT">https://github.com/BillJr99/pxt-ESP8266_VarIOT</a>.</p>

<p>That’s it!  You can now use VarIOT blocks to send data to the VarIOT gateway.  There are three basic steps (and blocks) to do this:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">on start</code>: add an <code class="language-plaintext highlighter-rouge">Initialize ESP8266</code> block.  The RX and TX pins should be set to P0 and P1, respectively (this seems backwards from the pinout wiring we did earlier, and that’s OK, because the Rx of the WiFi is the Tx of the micro:bit, and vice-versa!).  Set your WiFi SSID and password here.</li>
  <li>Also <code class="language-plaintext highlighter-rouge">on start</code>, but after connecting to WiFi, add a <code class="language-plaintext highlighter-rouge">Configure VarIOT gateway location</code> block and set the IP address and port of the VarIOT gateway.  For me, that is <code class="language-plaintext highlighter-rouge">rpi4-variot</code> and <code class="language-plaintext highlighter-rouge">5000</code>, but this will depend on the IP address of the Raspberry Pi and the port used when configuring the gateway above.</li>
  <li>Whenever you’d like, add an <code class="language-plaintext highlighter-rouge">Upload data to VarIOT</code> block and fill in the details.  For me, I used the following settings from when I configured the gateway:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Endpoint</code>: my_devices</li>
      <li><code class="language-plaintext highlighter-rouge">Device Name</code>: Mongan Gateway</li>
      <li><code class="language-plaintext highlighter-rouge">Label</code>: temp</li>
      <li><code class="language-plaintext highlighter-rouge">Value</code>: 55</li>
    </ul>
  </li>
</ol>

<p>That should do it!  When you view your gateway dashboard on the server, you should see this value appear.  Here is the code:</p>

<p><img src="https://github.com/billjr99/variot-test/raw/master/.github/makecode/blocks.png" alt="Finished Blocks for the VarIOT micro:bit integration test program" /></p>

<h2 id="programming-the-microbit-extension-to-connect-to-variot">Programming the micro:bit Extension to Connect to VarIOT</h2>
<p>You don’t have to do this step, but if you’re interested, this section describes how I created the micro:bit extension blocks for use in your micro:bit program (to connect to VarIOT).</p>

<p>To do this, I forked the <a href="https://github.com/alankrantas/pxt-ESP8266_ThingSpeak">alankrantas/pxt-ESP8266_ThingSpeak</a> repository into my own extension, which I call <a href="https://github.com/BillJr99/pxt-ESP8266_VarIOT">BillJr99/pxt-ESP8266_VarIOT</a>, under an MIT license.</p>

<p>All of the original repository’s WiFi connection and TCP connection code applies here, so I was able to re-use that.  In effect, we will run our cURL command that we saw above to send telemetry data to the Gateway, but we’ll get these values from a micro:bit!  I moved this code into a function called <code class="language-plaintext highlighter-rouge">doHTTP</code>, and created blocks that send an HTTP request to post data from the micro:bit as follows:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="cm">/**
    * Connect to VarIOT and upload data. It would not upload anything if it failed to connect to Wifi or VarIOT.
    */</span>
    <span class="c1">//% block="Upload data to VarIOT|Endpoint = %endpoint|Label = %label|Value = %value"</span>
    <span class="c1">//% endpoint.defl=mongan</span>
    <span class="c1">//% label.defl=temp</span>
    <span class="c1">//% value.defl=45</span>
    <span class="k">export</span> <span class="kd">function</span> <span class="nx">sendVarIOTTelemetry</span><span class="p">(</span><span class="nx">endpoint</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">label</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">value</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">body</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">{</span><span class="se">\"</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">label</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\"</span><span class="s2">: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">}</span><span class="dl">"</span>
        <span class="kd">let</span> <span class="nx">str</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">POST /</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">endpoint</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> HTTP/1.1</span><span class="se">\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">Content-Type: application/json</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">Content-Length: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">body</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">body</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n\r\n</span><span class="dl">"</span>
        <span class="nx">doHTTP</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="cm">/**
    * Connect to VarIOT and upload data given a device name. It would not upload anything if it failed to connect to Wifi or VarIOT.
    */</span>
    <span class="c1">//% block="Upload data to VarIOT|Endpoint = %endpoint|Device Name = %devicename|Label = %label|Value = %value"</span>
    <span class="c1">//% endpoint.defl=mongan</span>
    <span class="c1">//% devicename.defl="Mongan Gateway"</span>
    <span class="c1">//% label.defl=temp</span>
    <span class="c1">//% value.defl=45</span>
    <span class="k">export</span> <span class="kd">function</span> <span class="nx">sendVarIOTTelemetryByDeviceName</span><span class="p">(</span><span class="nx">endpoint</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">devicename</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">label</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">value</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">body</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">{</span><span class="se">\"</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">label</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\"</span><span class="s2">: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">value</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">sensorName</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">devicename</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\"</span><span class="s2">}</span><span class="dl">"</span>
        <span class="kd">let</span> <span class="nx">str</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">POST /</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">endpoint</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> HTTP/1.1</span><span class="se">\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">Content-Type: application/json</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">Content-Length: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">body</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n\r\n</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">body</span> <span class="o">+</span> <span class="dl">"</span><span class="se">\r\n\r\n</span><span class="dl">"</span>
        <span class="nx">doHTTP</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="cm">/**
    * Configure VarIOT gateway location
    */</span>
    <span class="c1">//% block="Configure VarIOT gateway location|URL/IP = %ip|Port = %port"</span>
    <span class="c1">//% ip.defl=rpi4-variot</span>
    <span class="c1">//% port.defl=5000</span>
    <span class="k">export</span> <span class="kd">function</span> <span class="nx">configureVarIOT</span><span class="p">(</span><span class="nx">ip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">port</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">variot_configured</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="nx">variot_ip</span> <span class="o">=</span> <span class="nx">ip</span>
        <span class="nx">variot_port</span> <span class="o">=</span> <span class="nx">port</span>
    <span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="pattan" /><category term="education" /><category term="technical" /><summary type="html"><![CDATA[In this article, we will integrate the BBC micro:bit with a ThingsBoard gateway called VarIOT.]]></summary></entry><entry><title type="html">Remake Learning Day: Equitable CS Education for Broader Workforce Preparation through Design Thinking and Ubiquitous Platforms</title><link href="https://www.billmongan.com/posts/2022/05/remakelearningday/" rel="alternate" type="text/html" title="Remake Learning Day: Equitable CS Education for Broader Workforce Preparation through Design Thinking and Ubiquitous Platforms" /><published>2022-05-18T00:00:00+00:00</published><updated>2022-05-18T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2022/05/remakelearningday</id><content type="html" xml:base="https://www.billmongan.com/posts/2022/05/remakelearningday/"><![CDATA[<p>In this article, we will explore tools that enable students to leverage technology in informal contexts that facilitate problem solving in preparation for diverse workforce pathways.  Technical solutions and automation aren’t just for Computer Science majors, and there exists a variety of platforms that support exploration and learning as well as productive applications of computing.  Our goal is to democratize computing skillsets across all disciplines, and to give students the tools they need to bring computing and technology to their favorite subjects.  This has the potential to enhance teaching and learning broadly, and to facilitate participation in computing with inexpensive (or free!) no-code or ubiquitous-code platforms.</p>

<p>We will sample some tools that have been developed and used in Southeastern Pennsylvania to enable students to explore computing concepts and discover solutions to authentic problems, with an aim toward building their confidence in creatively applying technology to workforce-relevant application domains.  We will connect these tools to potential careers and career pathways, and conclude with a survey of industry-grade no-code (or ubiquitous-code) platforms for further exploration.</p>

<h2 id="sections">Sections</h2>
<ol>
  <li><a href="#designthinking">Bridging the Classroom and Workforce through Design Thinking</a></li>
  <li><a href="#platforms">Platforms and Activities</a>
    <ol>
      <li><a href="#lostandfound">Lost-and-Found with the BBC Microbit</a></li>
      <li><a href="#rbrowser">R and SQL in the Browser</a></li>
      <li><a href="#replit">Replit in the Classroom</a></li>
      <li><a href="#variot">The Internet-of-Things (IoT): Analyzing Live Medical and Environmental Sensors with the VarIOT Platform at Drexel University</a></li>
    </ol>
  </li>
  <li><a href="#">Authentic Problems between Industry and K-20 for Broad Careers and Skills</a></li>
</ol>

<h2 id="bridging-the-classroom-and-the-workforce-through-design-thinking-">Bridging the Classroom and the Workforce through Design Thinking <a name="designthinking"></a></h2>

<p>Some problems are hard to approach because we lack the complete worldview required to solve them entirely.  These are problems that are larger than any one of us, and require collaborative and often multidisciplinary teams.  There is no “perfect” textbook solution, and so an iterative cycle of planning, trial, evaluation, and feedback is needed.  These authentic problems provide opportunities to develop and apply design thinking skills.</p>

<p align="center">
<img alt="Design Thinking Process Stages from interaction-design.org" src="https://public-images.interaction-design.org/literature/articles/heros/5808b55608af6.jpg?tr=w-1024" />
</p>

<p>Design Thinking seeks to understand the problem domain, which requires discussion with a variety of stakeholders [<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">1</a></sup>, <sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">2</a></sup>].  We’re not likely to know all the right stakeholder groups until we “live and learn,” and so this is an iterative and perhaps never-ending process.  One way to approach this is to experiment and prototype with potential solutions, and then “shop them” to your stakeholder groups for feedback.  Learning a new concept, whether it is a lesson on the Federalist Papers, the Pythagorean Theorem, or computer programming, can be considered an opportunity to learn and explore in-context through the Design Thinking process.  No-code platforms provide a mechanism to express those designs in an automated and collaborative environment.  In essence, we are applying inquiry and discovery learning through the use of technology platforms that are accessible to students across any discipline (in other words, not just the computer class). By viewing problems through the lense of the stakeholder, we bring empathy into the problem space, incorporate a “backwards design” by approaching problems from an outcomes perspective, and facilitate creative innovation in the classroom setting through role playing activities.</p>

<p>This approach enables collaboration across different classroom environments - not just between the STEM students and the context disciplines, but between entire environments.  For example, a rural school district familiar with regional challenges (for example, an agricultural or healthcare need) could engage with suburban and urban school districts with their own regional challenges (for example, traffic or environmental needs) to exchange broader perspectives and diversity of thought.  Often, the technology and platforms required to approach these challenges is similar across these problem spaces, and exchanging common ideas creates new pathways to collaboration as well as a diversification of perspectives.</p>

<h2 id="platforms-and-activities-">Platforms and Activities <a name="platforms"></a></h2>

<h3 id="lost-and-found-with-the-bbc-microbit-">Lost-and-Found with the BBC Microbit <a name="lostandfound"></a></h3>

<p>If you’ve ever lost your backpack, glasses, or other items, you might be aware of <a href="https://www.thetileapp.com/">tools</a> designed to help find those items by playing a sound and letting you see how close you are to the missing item.  By listening to the sound, you can track down where the missing item is.  As you get closer to the source, the sound becomes louder.  Sometimes, devices like these emit a radio signal - not an audible sound, but an invisible wave of light, that computers can sense with a radio antenna.  As you get closer to the source of the signal, the intensity of the signal increases, just like the sound did.  Using the <a href="https://microbit.org/">BBC Microbit</a> no-code platform, we will develop a system to communicate a radio signal between two devices to play a game of “hide and seek” where the goal is to locate the source of the invisible signal [<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">3</a></sup>].</p>

<p>First, students should plan a solution together: what steps are required to play a game of <a href="https://en.wikipedia.org/wiki/Marco_Polo_(game)">Marco Polo</a>?  Without requiring a technical background, some insights can emerge: every so often, the “source” generates a signal that the players can hear (but can’t see); this sound has a direction and intensity that helps the players move closer to the source.  As this repeats, if the sound becomes louder, we can infer that we are getting closer.</p>

<p>Let’s make a flow-chart of this idea and lay out the building blocks in the software environment.</p>

<p align="center">
<img alt="Hide and Seek Flowchart" src="/hide-and-seek/files/hideandseek.drawio.png" />
</p>

<p>We can build each of these steps in the <a href="https://makecode.microbit.org/">Microbit Makecode environment</a> [<sup id="fnref:4:1" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">3</a></sup>].  We will build this from scratch, but you can import it by clicking the Import button and importing from this URL: <a href="https://github.com/billjr99/hide-and-seek">https://github.com/billjr99/hide-and-seek</a>.  This environment includes a simulator that will run the project without physical hardware, but if you have the devices, they can be downloaded to a physical Microbit by clicking the three dots next to the Download button, and choosing “Connect to Device” from the menu that appears.  You’ll need two divices for this project (one to “hide” and one to “seek”).</p>

<h4 id="getting-started-sending-a-signal">Getting Started: Sending a Signal</h4>

<p>The project opens with two “functions” or slots that we can fill in with behaviors: “on start” and “forever.”  The “on start” function will allow us to set up items that we’d like to keep track of during the program, while the “forever” function will dictate what happens over and over.  From the flowchart, we can see that everything happens in this “over and over” loop - and is kicked off by sending a signal to our partner.  Under the “Radio” section of blocks on the left, there is a “radio send number” behavior that we can drag into the “forever” function.  We’re just measuring how loud the signal is: when playing “Marco Polo” the “signal” is to shout the word “Marco,” but in reality, it doesn’t matter what the word is!  We’re really listening for the intensity of the sound.  So, any number will do here.</p>

<p>In the “on start” section, use the “radio set group” to set what is called the “radio group” - this is like setting the channel on a walkie talkie so that each pair of radios can communicate in isolation.  Choose any number here, as it will be the same for both devices.</p>

<p>Finally, if you’re using a real physical Microbit device, using the radio continuously in the “forever” function will drain your batteries very quickly.  You can add a “pause” behavior in the “forever” loop to stop in between signals (imagine yelling “Marco” over and over continuously!  It’s OK to put a little break in between).  I put a 1000 millisecond pause into my loop, so it will send a signal every second.  Feel free to adjust this as you like.</p>

<h4 id="getting-warmer-or-colder">Getting Warmer or Colder?</h4>

<p>Whenever we receive a signal, we want to ask ourselves whether that signal is “louder” or “softer” than the last time we received it.  To do this, we will introduce the concept of “variables.”  These are values that we can remember over time.  We’re interested in remembering the intensity or “received signal strength indicator” (RSSI) of the last wireless radio signal we received, so we’ll create a variable called <code class="language-plaintext highlighter-rouge">lastRSSI</code>.  In the “on start” function, add a “set variable” behavior and create this <code class="language-plaintext highlighter-rouge">lastRSSI</code> variable.  You can give it any starting value that you like, since it will update over time.</p>

<p>We’ll compare this value to the new signal each time we receive one.  We’ll set the variable value to the current signal strength as well, so that next time, we can compare that, too.  This is like remembering in your mind how loud the “Marco” shout was the last time you heard it, and comparing it to what you’re hearing now.  Next time, you’ll compare it to what you just heard, and so on.</p>

<p>You can drag other functions into the canvas; under the “Radio” section, there is a function called “on radio received.”  This function will execute any time a new signal is received.  It comes with its own variable (known as a “parameter”), called <code class="language-plaintext highlighter-rouge">receivedNumber</code>.  This is the number that you sent during the “radio send number” behavior.  We don’t really need it: it doesn’t matter what the signal was (“Marco” or “Hello” or anything else!), but rather how loud it was!</p>

<p>On our flowchart, we want to ask whether this signal is louder or softer than the last one.  The current signal strength can be found in the “received packet: signal strength” behavior under the “Radio” section on the left.  Since we’re asking a question about this value, we can use the “if - else” behavior found under the “Logic” section.  In the if block, drag the “X &gt; Y” behavior from the “Logic” section and place it inside the diamond shaped space of the if behavior.  Inside that, you can place the “received packet: signal strength” value, and the <code class="language-plaintext highlighter-rouge">lastRSSI</code> variable.</p>

<p>If the current signal strength is greater than the last one, we’re getting closer!  I used the “show arrow: North” behavior to show this on the LED display, and dragged that inside the if block.  In the else block, we know that the signal is not stronger, so it is either weaker or the same.  I used the “show arrow: South” behavior to show this, and dragged it into the else portion of the if behavior.</p>

<p>Finally, after the if block, I added a “set <code class="language-plaintext highlighter-rouge">lastRSSI</code> to” behavior.  We want to set this variable to the current signal strength.  We checked this value just a moment ago, and itw as called “received packet: signal strength.”  Let’s set <code class="language-plaintext highlighter-rouge">lastRSSI</code> to the received packet signal strength.</p>

<p>And that’s it!  If you run this, you should see arrows on the two devices showing if they are getting “warmer” or “colder.”  With physical devices, you can walk around to see this in action.  On the simulator, there is a square “wave” in the top right simulating the radio activity.  You can click and drag left or right to increase or decrease the signal strength, and you should see the arrows update accordingly.</p>

<h4 id="optional-what-if-the-signal-strength-hasnt-changed">Optional: What if the Signal Strength Hasn’t Changed?</h4>
<p>The if behavior has a cousin called the “if - else if - else” block.  This allows us to ask multiple questions in the same if statement.  Previously, we asked if the signal was stronger than the previous one, and showed a “North” arrow if it was, and a “South” arrow if it was not.  But what if the signals are equal?  This if statement can ask if the new signal strength is stronger, and otherwise (“else”) ask if the new signal strength is weaker.  In the final “else,” we now know that the signal is neither stronger nor weaker, and must be the same.  I displayed a heart icon on the LED display if this was the case, indicating that we’re not getting closer or farther from the target right now.</p>

<h4 id="optional-seeing-the-signal-strength">Optional: Seeing the Signal Strength</h4>
<p>You may have seen the concept of RSSI when you choose a wireless network on your phone or computer.  Often, you’ll see the classic “bars” of signal strength, which is really just a visualization of the signal strength number that we’ve been comparing in this program.  To a computer, everything is a number, and sometimes, you might see the actual number on your computer or on your phone.  It’s measured in <a href="https://en.wikipedia.org/wiki/Decibel">decibels</a>, and these radio signals are often pretty weak (likely a numeric value between -40 and -100).  It’s still amazing to me that these faint invisible signals allow us to carry data over the internet, send music to our radios, and even communicate wirelessly around the world.  Even right now, there could be dozens or hundreds (or more!) waves of invisible light energy bouncing around you carrying wireless data by radio signal.</p>

<p>Let’s add a variable to store this value each time we receive a signal.  Based on what we’ve done already, where should we create this variable, and when should we update it?</p>

<p>Once you’ve added this variable to the “on start” function and set its value each time the “on radio received” function executes, you can drag another function to the canvas called “on button pressed” that we will use to show the current signal strength value whenever the A button is pressed.  Its behavior is just a single step: show the <code class="language-plaintext highlighter-rouge">currentRSSI</code> variable value.</p>

<h4 id="optional-setting-multiple-radio-groups">Optional: Setting Multiple Radio Groups</h4>
<p>Finally, if you’d like a whole class to use this at once, it is helpful to put each pair of radios into a unique radio group, so that two people can play together without “hearing” and displaying the signals from everyone else’s device.  To do this, you can change the number in the “radio set group” behavior to a unique value for each pair of devices.  Although that change alone is sufficient, I created a variable called <code class="language-plaintext highlighter-rouge">radioNumber</code> to do this, just for fun.  I had my radio send the radio group number in the “forever” loop, and added an if statement in the “on radio received” function to check that the <code class="language-plaintext highlighter-rouge">receivedNumber</code> parameter was equal to the <code class="language-plaintext highlighter-rouge">radioNumber</code> variable before checking the signal strength - almost like a little passcode to help ensure that the radio is hearing the correct partner.</p>

<h4 id="the-finished-project">The Finished Project</h4>

<p>Here’s my finished product:</p>

<p align="center">
<img alt="The finished hide and seek project in the Microbit Makecode" src="https://github.com/billjr99/hide-and-seek/raw/master/.github/makecode/blocks.png" />
</p>

<h4 id="extension-how-far-away">Extension: How Far Away?</h4>
<p>The signal strength will vary based on the distance between the two devices, although walls and other items in the room will also cause the signal strength to change.  Roughly speaking, though, the signal strength can be thought of as a proxy to the distance, just like the number of “bars” of signal strength is a rough estimate of how far you are from the cell phone tower or wireless access point (although other artifacts, such as being indoors, will also lower the signal strength and make it appear that you are even farther away).</p>

<p>Older or more experienced students could display the RSSI at a few known distances from their partner, and fit a linear function to those points to get a rough estimate of how changes in the signal strength correspond to distance.  Admittedly, the relationship is not truly linear and there are other variables involved, but it is a reasonable estimate using things we can measure.</p>

<p>Add a variable to the “on radio received” function that calculates the distance using the linear equation on the signal strength.  Change the “on button pressed” function to display this value instead!</p>

<h3 id="r-and-sql-in-the-browser-">R and SQL in the Browser <a name="rbrowser"></a></h3>

<p>During the pandemic, my colleague <a href="https://www.ursinus.edu/live/profiles/4502-christopher-tralie">Chris Tralie</a> developed a Javascript system that allows students to write code in their web browser, which is transpiled to JavaScript from their language of choice (for example, Java and Python) or compiles to WebAssembly and executes in the browser (for example, C++).  We connected these modules to our Learning Management System to assign <a href="https://www.billmongan.com/Ursinus-CS173-Spring2022/Modules/Functions/Exercise">class warmup exercises</a> that are autograded, and that provide “hints” through automated feedback about incorrect answers.</p>

<p>Using Dr. Tralie’s framework, I added support for the <a href="https://www.billmongan.com/Ursinus-CS173-Spring2022/assets/js/R/TutorialExercise">R statistical processing language</a> [<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">4</a></sup>] and the <a href="https://www.billmongan.com/Ursinus-CS377-Fall2021/Modules/SQL/Aggregation/Exercise">SQL database language</a>.</p>

<p>The addition of R and SQL allows students to experiment with database management and data analysis without requiring software installations on their local computer.  We will explore the classic <a href="https://en.wikipedia.org/wiki/Iris_flower_data_set">Iris flower dataset</a> from R.A. Fisher in 1936 using R [<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">5</a></sup>].</p>

<h4 id="classifying-data-from-features">Classifying Data from Features</h4>
<p>Imagine holding some coins in your hand.  Can you tell what they are without looking at them?  How can you tell?  You might look for some common “features” like the thickness or the diameter of the coin, or the weight, or even the ridges along the outside of the coin.  If the coin is in rough shape, there might be some variance in the data that causes you to guess incorrectly from time to time, but the idea is that we can predict a classification of data by evaluating some known features against examples we’ve seen in the past.</p>

<p>We will use the Iris flower dataset to predict the species of a flower using a few features such as the length and width of the petal.  To try this out, navigate to this <a href="https://www.billmongan.com/Ursinus-CS173-Spring2022/Activities/FlowerClassifier">flower classifier page</a> which is embedded below[<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">6</a></sup>]:</p>

<iframe src="https://www.billmongan.com/Ursinus-CS173-Spring2022/Activities/FlowerClassifier" height="900px" width="100%"></iframe>

<p>Fill in some numbers for the petal length, petal width, and sepal length for some imaginary flowers.  You can select the species for each flower click “Add” each time to plot the flowers by species on the 3D plot (which you can click to rotate and zoom).  Take a look at the plot: do your flowers group in a way that makes sense?  If the species data points are far away from each other when the species is different, but close together with those of the same species, there’s a good chance you can classify a “mystery flower” between these species choices.  Enter numbers for a “mystery flower,” select the species that you think it is, and click “Guess” to see if you can correctly predict them!  If not, try refreshing the page, and adding new example flowers whose measurements “group together” by species a bit more closely.  Do you have an easier time classifying between them now?</p>

<h4 id="exploring-the-iris-dataset-using-r-in-the-browser">Exploring the Iris Dataset Using R in the Browser</h4>
<p>R.A. Fisher’s Iris flower data is built into the R platform, and we have embedded an <a href="https://github.com/georgestagg/webR">R webassembly compiler</a> [<sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">7</a></sup>] into a <a href="https://www.billmongan.com/Ursinus-CS173-Spring2022/assets/js/R/TutorialExercise">webpage</a> [<sup id="fnref:2:1" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">4</a></sup>] for exploration in our courses that use the R language (embedded below).</p>

<iframe src="https://www.billmongan.com/Ursinus-CS173-Spring2022/assets/js/R/TutorialExercise" height="900px" width="100%"></iframe>

<p>We will begin by viewing the the Iris dataset for analysis, which is described in detail in <a href="https://gexijin.github.io/learnR/step-into-r-programmingthe-iris-flower-dataset.html">this article</a> [<sup id="fnref:1:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">5</a></sup>].  To do this, you can type this command into the R console: <code class="language-plaintext highlighter-rouge">head(iris)</code>, which displays the first few rows of the dataset.  It contains columns for the sepal length and width, and the petal length and width, as well as the species of that flower.  There are 50 Setosa flowers, 50 Versicolor flowers, and 50 Virginica flowers in the example dataset.</p>

<p>The species column is not numeric, so we can’t plot that.  However, we will use the species later to color code our plots.  For now, let’s extract the first four columns (the numeric features) into a “dataframe” variable, by typing: <code class="language-plaintext highlighter-rouge">df &lt;- iris[, 1:4]</code>.</p>

<p>You can graph the columns of this dataset against each other.  We’ll use two features at a time so we can more easily view the data on a 2D plot.  You can do this by typing: <code class="language-plaintext highlighter-rouge">plot(iris$Petal.Length, iris$Petal.Width, col=iris$Species)</code>, which plots the petal length against the petal width, and color codes them by species.  To see which color represents which species, you can add a legend to the plot by typing: <code class="language-plaintext highlighter-rouge">legend("topleft", levels(iris$Species), fill=1:3)</code>.  How well do you think the Petal Length and Petal Width can help you predict the species of a flower, and why?</p>

<p>Run these commands to see 2D plots of all of the columns against each other (this is called a “pairs plot”).  Type this command to generate it: <code class="language-plaintext highlighter-rouge">pairs(df, col = rainbow(3)[speciesID], labels=c("PetalLength", "PetalWidth", "SepalLength", "SepalWidth"))</code>.  Which features do the best job separating the flowers by species?</p>

<h3 id="replit-in-the-classroom-">Replit in the Classroom <a name="replit"></a></h3>

<p>Another free platform that I have come to like for web-based pair programming in a variety of languages is <a href="https://replit.com">replit</a>.  I wrote <a href="https://www.billmongan.com/posts/2021/03/replitclassroom/">an article</a> describing some helpful tools built into replit, including external library support, unit testing, version control, basic graphics, and a data store [<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">8</a></sup>].</p>

<h3 id="the-internet-of-things-iot-analyzing-live-medical-and-environmental-sensors-with-the-variot-platform-at-drexel-university-">The Internet-of-Things (IoT): Analyzing Live Medical and Environmental Sensors with the VarIOT Platform at Drexel University <a name="variot"></a></h3>
<p>I work with a broadly multidisciplinary team on developing <a href="https://www.billmongan.com/portfolio/smartgarments/">textile-based wearable computing systems</a>.  Imagine if the “radio” you experimented with earlier using the Microbit could be embedded into your shirt, or into a baby’s onesie, and it was so small that you didn’t know you were wearing it!  These ubiquitous computing systems are generally known as the “Internet of Things” (IoT), and are interconnected smart devices that we can use to measure physiological functions like heart or respiratory rate, environmental measurements like air quality, or highway sensors for traffic management.  Here, computer scientists team up with physicians, sociologists, designers, engineers, and more, to create solutions that no one group could do on their own.  My colleagues at Drexel University run a program using the <a href="https://drexel.edu/engineering/news-events/news/archive/2021/December/vip-project-helps-student-make-connections/">Vertically Integrated Projects model</a> to provide opportunities for students across majors to blend their classroom experience with authentic problem solving and research skills.  In my faculty role at Ursinus College, my students and Drexel’s students collaborate virtually, develop solutions, conduct experiments, and present results, by bringing their own unique classroom and major experiences to the team.</p>

<p>The National Science Foundation (NSF) Science Nation series <a href="https://nsf.gov/news/mmg/mmg_disp.jsp?med_id=132437&amp;from=">featured</a> Drexel’s smart fabric Bellyband and the team.  The video can be found by clicking below:</p>

<p><a href="https://www.youtube.com/watch?v=aBUmWD0B8z0"><img src="https://img.youtube.com/vi/aBUmWD0B8z0/0.jpg" alt="https://www.youtube.com/watch?v=aBUmWD0B8z0" /></a></p>

<h2 id="authentic-problems-between-industry-and-k-20-for-broad-careers-and-skills-">Authentic Problems between Industry and K-20 for Broad Careers and Skills <a name="authentic"></a></h2>
<p>By approaching computing education from a Design Thinking perspective, we can reinforce the “human in the loop” and promote empathetic design choices early in the educational journey.  However, this approach also enables viewing problems through a broader lens in which technology is a means rather than an end.  Authentic problems can be shared from industry partners, who can participate in a low-committment way by serving as project stakeholders, providing user perspectives to students who can work in multidisciplinary teams to develop solutions.</p>

<p>Another benefit to this approach is that it celebrates the variety of skills inherent in computing; we often think of “programming” as the fundamental skill of computing.  And although computer programming is a ubiquitious tool that should be universally understood to facilitate communication about how the computer solves problems, it is not necessary to write code as part of a computing career.  One could imagine <strong>software design</strong> as a core aspect of problem solving with technology, by designing user-centric workflows and determining the basic steps and modules required within a software system, or <strong>user experience (UX)</strong> design, which centers on interfacing with humans to develop systems that are optimally usable within the constraints of our physiology and psychology, or <strong>quality assurance and software testing</strong> which plans how to validate that software works correctly, and identifies deficiencies in the software.  <strong>Information Technology (IT)</strong> specialists and <strong>systems administrators</strong> (and <strong>database administrators</strong>) ensure that the operational aspects of managing and maintaining the development environment, and <strong>front end developers</strong> create graphical user interfaces and web-based designs that allow one to engage with the software.  <strong>Project Managers</strong> coordinate the team and ensure that these groups are collaborating efficiently.  On top of this, each of these experts must also coordinate with domain experts who understand the target applications of computing systems.  All of these people work together to solve problems collaboratively, and each brings a unique and essential skillset to the team.  Just as the Vertically Integrated Projects model allows for the integration of and collaboration among a diverse set of technological and domain skillsets, computing and design thinking allow and encourage people to consider problems that are “bigger than themselves.”</p>

<p>You can find out more about these career paths and more <a href="https://www.computerscience.org/careers/">here</a>!</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:6" role="doc-endnote">
      <p>https://www.billmongan.com/Ursinus-CS474-Spring2022/Activities/DesignThinking <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">
      <p>https://public-images.interaction-design.org/literature/articles/heros/5808b55608af6.jpg?tr=w-1024 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>https://www.billmongan.com/hide-and-seek/ <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>https://www.billmongan.com/Ursinus-CS173-Spring2022/assets/js/R/TutorialExercise <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>https://gexijin.github.io/learnR/step-into-r-programmingthe-iris-flower-dataset.html <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>https://www.billmongan.com/Ursinus-CS173-Spring2022/Activities/FlowerClassifier <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">
      <p>https://github.com/georgestagg/webR <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>https://www.billmongan.com/posts/2021/03/replitclassroom/ <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="pattan" /><category term="education" /><category term="technical" /><summary type="html"><![CDATA[In this article, we will explore tools that enable students to leverage technology in informal contexts that facilitate problem solving in preparation for diverse workforce pathways. Technical solutions and automation aren’t just for Computer Science majors, and there exists a variety of platforms that support exploration and learning as well as productive applications of computing. Our goal is to democratize computing skillsets across all disciplines, and to give students the tools they need to bring computing and technology to their favorite subjects. This has the potential to enhance teaching and learning broadly, and to facilitate participation in computing with inexpensive (or free!) no-code or ubiquitous-code platforms.]]></summary></entry><entry><title type="html">Replit in the Classroom</title><link href="https://www.billmongan.com/posts/2021/03/replitclassroom/" rel="alternate" type="text/html" title="Replit in the Classroom" /><published>2021-03-20T00:00:00+00:00</published><updated>2021-03-20T00:00:00+00:00</updated><id>https://www.billmongan.com/posts/2021/03/replitclassroom</id><content type="html" xml:base="https://www.billmongan.com/posts/2021/03/replitclassroom/"><![CDATA[<p>In this workshop, we will explore opportunities to utilize <a href="https://replit.com">Replit</a> in the classroom for both small classroom exercises and assignments.  We will integrate Replit projects with additional tools and techniques including GitHub Classroom and POGIL instructional methods.</p>

<h2 id="using-external-libraries">Using External Libraries</h2>
<p>Using Replit, you can add external library support with many languages.  For example, if you import a package, Replit will automatically download and import the corresponding library when you compile or run the project.  However, if you have your own custom library that you’d like to import, you can do this as well.</p>

<p>If you are using a Java project, you can upload a jar file to your project (in my example, I uploaded a file called <a href="https://www.billmongan.com/Ursinus-CS173/files/asmt-minicrypto/rsamath.jar">rsamath.jar</a> to a directory called <code class="language-plaintext highlighter-rouge">lib</code>).  You will have to tell Replit how to compile your project with this library in your <code class="language-plaintext highlighter-rouge">CLASSPATH</code>, and we can do this by creating a configuration file within the project called <code class="language-plaintext highlighter-rouge">.replit</code>.  Add the following text to this file [<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>]:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>run = "export CLASSPATH=\".:lib/rsamath.jar\"; javac -d . Main.java; java Main"
</code></pre></div></div>

<p>This adds the jar to your classpath along with the current project directory, so that your Main.java can compile (in the main project directory) while also loading the library from the rsamath.jar file.  The remaining javac and java commands are the standard compilation and execution commands that replit would use by default.</p>

<p>Here is an example project [<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>] that computes a value and its modular inverse from prime number inputs:</p>

<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/MiniCrypto?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>

<h3 id="database-access">Database Access</h3>

<p>While we’re at it, Replit provides a key/value data store to each project that we can use to store those generated keys, in case you’d like some values to persist in between runs of a student’s project.  Replit provides a few web API endpoints via an environment variable that it maintains for your project, including:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">GET $REPL_DB_URL</code>: Insert a key and value into the datastore.  The data passed with the body of this HTTP request is <code class="language-plaintext highlighter-rouge">key=value</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">GET $REPL_DB_URL/key</code>: Retrieve the value associated with the key.</li>
  <li><code class="language-plaintext highlighter-rouge">DELETE $REPL_DB_URL/key</code>: Delete the key from the datastore.</li>
</ul>

<p>I have encapsulated these web calls [<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>] into a class called <code class="language-plaintext highlighter-rouge">ReplDb</code> that are provided in the following example project:</p>

<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/MiniCryptoDb?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>

<h2 id="version-control">Version Control</h2>

<p>Replit maintains a history of the revisions made to each project, but integrating with <a href="https://git-scm.com/">git</a> provides an opportunity to teach the fundamentals and mechanics of version control.  Under the Version Control left menu of the Replit project page, you can create a git repository as follows:</p>

<p><img src="/files/media/2021-03-20-replitclassroom/versioncontrolsetup.png" alt="" /></p>

<p>When the repository is created, you will see something like this:</p>

<p><img src="/files/media/2021-03-20-replitclassroom/versioncreated.png" alt="" /></p>

<p>As you revise the project, you’ll have an opportunity to click to create a new revision, including a log message, that you can use to revert your project to any commit that you’ve made.</p>

<p><img src="/files/media/2021-03-20-replitclassroom/versioncontrolrevision.png" alt="" /></p>

<p><img src="/files/media/2021-03-20-replitclassroom/versioncontrolcommitted.png" alt="" /></p>

<p>Finally, you can connect this repository to your <a href="https://github.com/">GitHub</a> account.  This may not be appropriate for homework assignments so that they aren’t inadvertantly made public (more on this below!), but is a nice way to create a public portfolio of independent study projects and final course projects.</p>

<p><img src="/files/media/2021-03-20-replitclassroom/connecttogithub.png" alt="" /></p>

<h2 id="using-graphics">Using Graphics</h2>

<p>Replit supports graphical displays through the browser!  You can download Robert Sedgewick’s <a href="https://algs4.cs.princeton.edu/code/algs4.jar">algs4.jar</a> library [<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>] and add it to your project as we described above.  Running the project will display the output window as a frame within your web browser.</p>

<p><img src="/files/media/2021-03-20-replitclassroom/graphics.png" alt="" /></p>

<p>Here is an example project demonstrating this [<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>].</p>

<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/StdDrawPrincetonExample?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>

<h3 id="animations">Animations</h3>

<p>You can use animations with this library as well!  It’s a little slow through the browser, but you can clear the screen and draw lines iteratively to create fun animations.  Here’s an example:</p>

<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/GraphDemo?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>

<h2 id="unit-testing">Unit Testing</h2>

<p>We will see later that you can add your own unit tests and input/output tests in order to autograde a Replit project, but you can encourage students to create their own unit tests, too.</p>

<p>Click the checkmark on the left menu, and click the setup button to initialize your tests.  It will prompt you to add these imports:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.junit.Before</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.After</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">Assert</span><span class="o">.*;</span>
</code></pre></div></div>

<p><img src="/files/media/2021-03-20-replitclassroom/unittestsetup.png" alt="" /></p>

<p>Then, click “Add Test” under the same checkmark menu.   Add your test code, for example:</p>

<p><img src="/files/media/2021-03-20-replitclassroom/unittestexample.png" alt="" /></p>

<p>We will create the following test:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Main</span> <span class="n">tester</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Main</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">tester</span><span class="o">.</span><span class="na">squareIt</span><span class="o">(-</span><span class="mi">2</span><span class="o">);</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="n">result</span><span class="o">);</span>
</code></pre></div></div>

<p><img src="/files/media/2021-03-20-replitclassroom/unittestsquare.png" alt="" /></p>

<p>Finally, click Run Tests under the checkmark menu for a report.  I have not found a way to export these tests for submission, but you could ask students to copy their test code snippets and a screenshot of their report for a low-tech solution.</p>

<p>Here is a quick example project demonstrating how to create and run a unit test:</p>

<iframe height="600px" width="100%" src="https://repl.it/@BillJr99/JUnitExample?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe>

<p>If you are using a custom <code class="language-plaintext highlighter-rouge">.replit</code> file with external libraries, you can add this jar to your <code class="language-plaintext highlighter-rouge">CLASSPATH</code> in your <code class="language-plaintext highlighter-rouge">.replit</code> project: <code class="language-plaintext highlighter-rouge">/run_dir/junit-4.12.jar</code>.</p>

<h2 id="code-formatting">Code Formatting</h2>

<p>My students often miss the “auto-format” button on their Replit projects.  This is a nice way to clean up things like indentation, and to demonstrate best practices.  The button is located on the top right of the code window for the project.</p>

<p><img src="/files/media/2021-03-20-replitclassroom/autoformat.png" alt="" /></p>

<h2 id="pair-programming">Pair Programming</h2>

<p>Another often overlooked feature is project sharing functionality.  Click the “Share” button on the top left of the Replit project to invite other users to pair program with you simultaneously.  Note that this button might disappear if an ad-blocker plugin is used on the browser.</p>

<p><img src="/files/media/2021-03-20-replitclassroom/share.png" alt="" /></p>

<h2 id="embedding-replit-into-pogil-style-activities">Embedding Replit into POGIL Style Activities</h2>
<p>In my classes, I use a <a href="https://cspogil.org/Home">POGIL</a> style method to facilitate class discussion.  Instead of lecture notes, each class features some guiding questions, as well as some examples or materials to spark some curiosity and discussion.  As you have seen in this article, I also embed Replit project examples that students can view, fork, modify, and share with me from within the activity page.  You can do this by embedding an <code class="language-plaintext highlighter-rouge">iframe</code> in your web pages with a <code class="language-plaintext highlighter-rouge">src</code> tag indicating your Replit project URL (with <code class="language-plaintext highlighter-rouge">?lite=true</code> at the end of the URL), as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;iframe height="600px" width="100%" src="https://repl.it/@BillJr99/MiniCrypto?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"&gt;&lt;/iframe&gt; 
</code></pre></div></div>

<p>There is a button at the top right of the frame to open the example in a new browser tab.  Here is an <a href="https://www.billmongan.com/Ursinus-CS173/Activities/Conditionals3">example lesson</a>.</p>

<h2 id="integrating-replit-with-github-classroom">Integrating Replit with GitHub Classroom</h2>

<p>In addition to creating a version control repository, you can configure your assignments to automatically create repositories linked to your students’ Replit projects that are shared with you for grading and auto-grading.  One nice feature of <a href="https://classroom.github.com/">GitHub Classroom</a> integration is that you can configure these repositories to be private (although making the underlying Replit projects private may require a paid subscription).  I created a short video demonstrating how to set this up in a <a href="/posts/2020/08/replitgithubclassroom/">prior article</a>.</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/9gzm2MS4DHg" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>https://replit.com/talk/ask/Jar-files/21299 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>https://www.billmongan.com/Ursinus-CS173/Assignments/MiniCrypto <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>https://www.baeldung.com/java-http-request <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>https://introcs.cs.princeton.edu/java/stdlib/ <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>https://www.billmongan.com/Ursinus-CS173/Assignments/Faces <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bill Mongan</name><email>billmongan+website@gmail.com</email></author><category term="csta" /><category term="education" /><category term="technical" /><summary type="html"><![CDATA[In this workshop, we will explore opportunities to utilize Replit in the classroom for both small classroom exercises and assignments. We will integrate Replit projects with additional tools and techniques including GitHub Classroom and POGIL instructional methods.]]></summary></entry></feed>