Private RoIP (Radio over IP) System

I often work in places that are spread out over large distances and are underground or filled with radio obstructions like thick concrete/steel walls and lots of metal equipment. Getting radios to work takes some infrastructure and money.

Something I usually have access to at these sites: a reliable, isolated, fast network that goes wherever the equipment is at. So I wondered if anyone connected radios to a network to ‘extend their range’. One evening down the rabbit hole taught me that yes, of course, they did! I came across the application “app_rpt” that works with the open-source PBX software “Asterisk“, and that seemed like a good way to go.

Ten years ago, there was a Linux distro freely available that made Asterisk setup pretty simple. I guess they figured out how hard it is to make money giving away free software, so that is no longer available. Then I found the “AllStarLink” project. The ASL has a Linux distribution that has everything you need, all packaged up and ready to go. It is made for amateur radio use over the Internet, but with a little modification, it will work on a local network also.

Server

The server is also a node, and I’ve also seen it called a hub. In my case, it is a virtual machine running ASL beta2.0 and configured to be ‘radioless’ which means the radio type is set to “dahdi/pseudo”pitfall warning: don’t bother trying to use the “stable” ASL 1.01 images. Asterisk won’t start due to a conflict. Best I can tell the Debian updates caused it to break.

in the file /etc/asterisk/rpt.conf, add node definitions for all the other nodes on your local network. For example, the server node is 1500, and my other node is 1501. The default port is 4569

[nodes]
1500 = radio@127.0.0.1:4569/1500,NONE
1501 = radio@192.168.7.7:4569/1501,NONE

in the file /etc/asterisk/iax.conf, comment out the line by adding a “;” to the beginning of the line in the [general] stanza pointing to the register.allstarlink.org site. This will prevent the server node from attempting to connect. (we are all local, no internet activity)

Node(s)

The nodes are a raspberry pi loaded with the ASL Beta2.0 image with a special USB radio interface. The interface is a sound card that converts the audio and also handles two extra signals needed to make radio communications work:
1) PTT (push to talk) – this is used when the node wants the radio to transmit
2) COS (carrier operated switch) – the radio turns on this signal when the squelch is open to notify the node that someone is talking

I found 2 options for pre-assembled units in the $50 range:
1) Repeater Builder USB RIM Lite: repeater-builder.com/products/usb-rim-lite
2) ARA-1 Sound FOB by TechNo By George: technobygeorge.com

When looking for radios, keep a few things in mind:
1) the radio needs to provide a COS signal – I found one that I didn’t have to open up and modify, I could just plug into the speaker/mic jack to get all four signals I need (audio in/out, PTT, COS)
2) you need a license to use most radios (FRS is an exception)

in the file /etc/asterisk/rpt.conf, add node definitions for all the other nodes on your local network. For example, the server node is 1500, and my other node is 1501.

[nodes]
1501 = radio@127.0.0.1:4569/1501,NONE
1500 = radio@192.168.7.6:5469/1500,NONE

in the file /etc/asterisk/iax.conf, comment out the line by adding a “;” to the beginning of the line in the [general] stanza pointing to the register.allstarlink.org site. This will prevent the node from attempting to connect. (we are all local, no internet activity)

Laptop Software

If you are already sitting in front of a computer that is connected to the ‘radio’ network, there is really no reason you need a radio, your computer can run software that interfaces with Asterisk. I found a windows program called “IaxRpt” that runs on Linux using WINE. It is basically a SIP softphone application with PTT functionality.

To use this software, a few edits have to be made to your server node. In the /etc/asterisk/iax.conf file, find or add the stanza below and set up a password in the ‘secret =’:

[iaxrpt]
type = user
context = iaxrpt ; Context to jump to in extensions.conf
auth = md5
secret = xxxxxxxxxx
host = dynamic
disallow = all
allow = ulaw
allow = adpcm
allow = gsm
transfer = no

in my version of ASL, the /etc/asterisk/extensions.conf is setup with slick variables that get auto-populated with the correct node number and didn’t require any modifications as the wiki instructions indicated

[iaxrpt] ; entered from iaxrpt in iax.conf
exten => ${NODE},1,rpt(${NODE}|X)

When creating an account in IaxRpt, use the server’s node number (1500 in my case) as the “account” or “name”. (this was not obvious to me and is required for it to connect) The “host” is the IP address of the server node and the “username” is “iaxrpt”. The “password” is whatever you set “secret” to in the step 3 above here.

Click ‘connect’

Then use the keypad in the software to dial “* 3 1501” (replace 1501 with your node number) — this command connects the server node to the other node. It took me a while to figure out this step was required. I know there are other ways of connecting the two nodes, but the important thing to know is that you have to command it to happen.

Click the “TRANSMIT” button to talk to the other node, which will key up its radio and transmit your voice remotely.

Posted in How-To, Projects, Technology | Tagged , , , , , , , | 4 Comments

ODE to Poison Ivy

oh poison ivy!
oh poison ivy!

let me explain,
how much I’m in pain

you grow so well,
but you create a hell

your oil is attracted to my skin,
and then works to dissolve me again

oh poison ivy!
oh poison ivy!

let me explain,
you are making me insane!

Posted in Outdoors, Rant | Tagged , | Leave a comment

MQQT using Node-RED

When I started searching for how to transfer data between 2 computers using MQQT, I saw articles talking about Node-RED, but I dismissed it as being some sort of toy for children. That was probably a mistake because once I installed it, I realized it was both genius and a powerful tool. It lets you do amazing, complicated things without having to get into the weeds in every little detail.

In the screenshot below, the NR session from my Raspberry Pi is on top. The first block monitors a file to see if it changed. When it sees a change, it fires the next 2 blocks, which read the data I am looking for from a file. That data is passed on to the 3rd set of blocks (in purple) which publish the data via an MQQT broker running on the same computer. (mosquitto) The green block is just a debug block and is not needed.

The NR session on the bottom is running on my desktop. A single block (MQQT in) connects to the broker running on the Raspberry Pi and reads to values.

Here is python code running on the Raspberry Pi that retrieves the values and stores them in a pseudo-file. (the /var directory in a *nix machine is actually a location in RAM, not on the HDD or SD card)

#!/usr/bin/env python3
########################################################################
# Filename    : myProg2.py
# Description : write pi ADC values to file
# Author      : bg
# modification: 20210504
########################################################################

from time import sleep
import myADC
 
mydata = [0,0,0,0,0,0,0,0]  #create python list

def loop():
        while(True):
            for i in range(8):
                mydata[i] = myADC.getValue(i)  #bring ADC values into list
                filename = '/var/adc/'+str(i) #create the filename
                ramdisk = open(filename,'w')  #open file in writeover mode
                ramdisk.write(str(mydata[i])) #write data to file
                ramdisk.close()
            sleep(5)

if __name__ == '__main__':
    print ('Program is starting ... ')
#     print(myADC.getValue(0))
#     print(myADC.getValue(1))
    try:
        loop()
    except KeyboardInterrupt:
        #destroy()
        pass
Posted in Industrial Automation | Tagged , , , | Leave a comment

Raspberry Pi Data to Google Sheets

A Raspberry Pi 3B is configured to bring in sensor data through a ADC (analog to digital converter) connected to the pi’s I2C (Inter-Integrated Circuit) serial interface. (I’m using the ADS7830 module that came with a kit that contained all kinds of fun project goodies). I have 2 potentiometers wired to the ADC that I can turn by hand to give me 2 real voltage values. The pi has internet access through wifi.

Raspberry Pi 3B connected to a prototyping breadboard with the ADS7830 module and 2 potentiometers. A backlight LCD is also shown, but not part of this project.

ADC to Pi

I followed the kit‘s instructions for the python language to

  • enable the I2C interface in the Pi’s configuration
  • install the needed os packages
  • import the needed python modules
  • wire the ADC module to the Pi
  • wire the pots to the ADC

I wrote a function so I can get the ADC values from other programs:

#!/usr/bin/env python3
########################################################################
# Filename    : myADC.py
# Description : Use ADC module to read the voltage values
# Author      : sparkygeek.com
# modification: 2020/04/26
########################################################################
from ADCDevice import *
adc = ADS7830()
    
def getValue(chan = 0):
    value = adc.analogRead(chan)    # read the ADC value of channel 0
    voltage = value / 255.0 * 3.3  # calculate the voltage value
    print ('Channel %d ADC Value : %d, Voltage : %.2f'%(chan,value,voltage))
    return voltage

Pi to Google

Follow the directions for getting the Google Python Quickstart project working. (Enable Google Cloud Platform Project API, Create credentials, download your credential file, and install the needed Google client libraries) The quickstart code uses the spreadsheets.values.get method, but we are going to use the spreadsheets.values.append method to add new values to our spreadsheet. Here is the code I’m using to do this:

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import myADC
from time import sleep

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']

# The ID and range of a sample spreadsheet.
SPREADSHEET_ID = 'xxxxxxxxxxRb6BXNBORw72feWN3iiaz_xxxxxx'
RANGE_NAME = 'Sheet1'

def main():
    """Shows basic usage of the Sheets API.
    Prints values from a sample spreadsheet.
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    service = build('sheets', 'v4', credentials=creds)

    # Call the Sheets API
    sheet = service.spreadsheets()

    newData = [myADC.getValue(0),myADC.getValue(1)]  # get data from ADC module

    body = {
            "values": [
                newData
            ]
           }
    result = sheet.values().append(
           spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME,
           valueInputOption='USER_ENTERED', body=body).execute()
    print('{0} cells appended.'.format(result \
                                          .get('updates') \
                                          .get('updatedCells')))

if __name__ == '__main__':
    while(True):
        main()
        sleep(2.5)

See it in action!

Posted in Industrial Automation, Projects | Tagged , , , , | Leave a comment

Ignition User Pop Up Data Entry Box

If you want a data-entry pop-up in Inductive Automation’s Ignition, you just call the function to open an input box. This seems simple to computer programmers, but it is different than how typical industrial automation systems accomplish the task. Ignition marries the two worlds, which is one of the things that makes it awesome.

the blue button at the top of the screen has a script that executes when pressed
the script calls the input box, converts to a float, then stores the value in a system tag
standard input box with the parameters from the script
Posted in Industrial Automation | Tagged , , | 2 Comments

My Political Opinions Lean…

My political opinions lean more and more to Anarchy… The most improper job of any man, even saints, is bossing other men. Not one in a million is fit for it, and least of all those who seek the opportunity. -JRR Tolkien

Posted in Politics | Tagged , , | Leave a comment

Shop Construction Photos

Posted in Projects | Tagged , , | Leave a comment

Bathroom Remodel Pictures

Posted in Projects | Leave a comment

Getting Ambient Weather Data into Ignition

I wanted to learn how API’s work, so I figured bringing in some of my weather station data to an Inductive Automation’s Ignition project would be a good task to learn on. I wasted a lot of time trying to use the python helper functions that were recommended in the docs at https://ambientweather.docs.apiary.io/. The problem with that route was that Ignition uses it’s own instance of python (actually jython)and it’s an older version. Note: if you do need to import libraries, I finally learned you can use the pip install command which takes care of all the dependencies for you if you follow the directions at https://forum.inductiveautomation.com/t/procedure-for-installing-python-libraries-from-source/26022/2. I almost had it working, but ran into a problem with a codec, which I didn’t understand and didn’t want to figure out.

I wound up using Ignition’s system.net.httpClient() function to make the call to the API. Once I figured out how to format the API call, the hard part was figuring out how to pick through the JSON document it returned and update tag values within Ignition with those values. I couldn’t get any of the built-in functions that are supposed to help with this to work correctly. The documentation is pretty sparse on those functions and it seemed to get hung up on the fact the date values contained colons. My workaround was to convert it to a big string value, then use python’s string commands to find the parts I wanted.

Below is the script I use to do the work. The script runs every 5 seconds. One of the software developers at Ignition had a much more elegant script started, but the get_data() method had a problem and I just went with a simpler solution.

#get data from ambient weather API
client = system.net.httpClient()
response = client.get("https://api.ambientweather.net/v1/devices/00:0E:00:00:00:00", params={"apiKey":"xxx","applicationKey":"yyy","limit":"1" }) 
#comes in as a list
weatherdata = response.json
#convert to a string to deal with problem of time having colons
weatherdataString = ' '.join(map(str, weatherdata)) 

#change searchterm for each element you want
searchterm = "tempf"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/temperature",(weatherdataString[startpoint+offset:endpoint-2]))

#change searchterm for each element you want
searchterm = "yearlyrainin"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/yearlyrain",(weatherdataString[startpoint+offset:endpoint-2]))

#change searchterm for each element you want
searchterm = "dailyrainin"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/dailyrain",(weatherdataString[startpoint+offset:endpoint-2]))

Posted in Industrial Automation | Tagged , , | Leave a comment

Loft Ladder

I built a ladder out of 2×4 and 1×4 lumber. It has 9 rungs spaced 12-3/4″ apart and is set to be used at a 70ยบ angle and have 9′-1″ of elevation.

Posted in Projects | Tagged , | Leave a comment