Polling for Problems or Other Interesting Information
Is march nearly over already?!?! The first quarter of 2025 has zoomed on by! I have been busy on other things and haven't had anything I thought that was worthy to share. Until now...
I recently created a program reminiscent of those side-scrolling LED marquees you see in various public places. Perhaps they have all been replaced with computer monitors? Well, I wanted one for my X desktop that I could post various interesting bits of information too. My original idea, from many years ago, was simply to keep abreast of tech news with RSS feeds. But then I realized it could have many other practical notification purposes.
After I had the program running the next question became: how do I aggregate and format notices from various long-running sensor programs? Pi Shack BASIC (psBASIC) came to the rescue. The main requirement was to build a mechanism to launch multiple programs and create a non-blocking event loop to poll them for their output.
For this discussion a sensor program can be any program that runs indefinitely, periodically checking something and reporting a status message to its STDOUT
. These programs can be written in any language you want, to watch anything you want, reporting any significant change.
For demonstration purposes I'll fall back to my original RSS feed idea. The sensor program is going to be the readily available rsstail
. This program will immediately report a feed's new article list, stay running and report any new articles as they are discovered. The new articles will be our status change, that we'll format and pass on to our notification system.
In my case the notification system is my side scrolling marquee X desktop fixture. But the notifications could be emails, SMS messages, IRC chats, web services, printed pages, blinking LEDs, ... anything you have a program to communicate with. My marquee program has a FIFO (pipe) I write to, to add messages to the scrolling list.
A simple 1 sensor (feed) solution
' Open marquee's pipe
OPTION LPRINT "~/.marquee.fifo"
' Open RSS feed
OPEN "i",#1,"|rsstail -N -u 'http://feeds.feedburner.com/TheHackersNews'"
DO UNTIL eof(1)
' Read the title.
LINE INPUT #1,t$
' Send it to the marquee
LPRINT t$
' Show the title on our terminal
PRINT t$
LOOP
' "rsstail" never ends, unless killed or it crashes.
In this case the OPTION LPRINT
and LPRINT
commands are my notification mechanism. I could have used a normal OPEN
and PRINT #
instead, but since I'm not using an actual printer in this case, LPRINT
is just a simple and convenient short cut. I don't want to get lost in all the different ways psBASIC can be used to send some kind of alert or status message, since the focus of this article is on polling the sensors. But in typical UNIX fashion the sky is the limit on how you can alert yourself or others.
The problem
We can open more than one sensor (ie. rsstail
) but each call to LINE INPUT#
blocks until content is received. That would mean that a loop to read from each will block until a response from one, before moving on to the next. That means that an alert will be hidden behind a non-alert condition, assuming that the sensor programs only report change alerts and remain silent in non-alert situations. But even if they report a status every N seconds the cumulative delays could be significant. Most all of my sensor code only reports on status change, just like our friend rsstail
.
Of course you could launch multiple copies of this program, one for each feed. But that is soooo stone age. And the goal of this article is to aggregate... So let's do more than one sensor in the same program!
The solution
psBASIC allows us to use the inkey$
function on any input stream. This seemed odd to me at first since my original computers used inkey$
to read a specific hardware device, the keyboard. On the TRS-80 this was a key switch matrix, that would get scanned and the first key down was reported. In the IBM PC this was the keyboard receiving 8051's scancode buffer.
In UNIX things are different and more universal. Keyboard input comes in on a File Descriptor (FD) just like any other kind of input (files, sockets, FIFOs, UARTs, ...). Originally keyboard input was from a TeleTYpe (TTY) attached to a serial port. So to implement inkey$
for keyboard access you've already made the tool to do a non-blocking check on any other source of input. That is the UNIX way! Generic building blocks that can be applied to any similar situation.
So we can use inkey$
to check for incoming input without blocking, but with the understanding that if there is input, we already have consumed its first byte.
Let's see it in action:
OPEN "i",#1,"|rsstail -N -u 'http://feeds.feedburner.com/TheHackersNews'"
OPEN "i",#2,"|rsstail -N -u 'https://www.technewsworld.com/rss-feed'"
DO
' Check feed (sensor) #1
s$ = inkey$(0,1)
IF s$<>"" THEN
' Read the title.
LINE INPUT #1,t$
' Prepend our first byte
t$=s$+t$
' Send notice
' ...
END IF
' Check feed (sensor) #2
s$ = inkey$(0,2)
IF s$<>"" THEN
' Read the title.
LINE INPUT #2,t$
' Prepend our first byte
t$=s$+t$
' Send notice
' ...
END IF
LOOP : ' Never ends. Sensors run forever
OK! That is just begging to be rolled into a loop, especially if you don't want to hard code your sources. More on that in a bit.
DEFINT a-z
OPEN "i",#1,"|rsstail -N -u 'http://feeds.feedburner.com/TheHackersNews'"
OPEN "i",#2,"|rsstail -N -u 'https://www.technewsworld.com/rss-feed'"
DO
FOR f=1 TO 2
s$=inkey$(0,f)
IF s$<>"" THEN
' Read the title.
LINE INPUT #f,t$
' Prepend our first byte
t$=s$+t$
' Send notice
' ...
END IF
NEXT
LOOP
Now we basically have a non-blocking event loop watching two RSS feeds (sensors). You could make it more restful if you know that your sensors have a significant delay between reports by adding a SLEEP
of some reasonable duration after the NEXT
.
Since we now have a loop for reading input we can open any number of sensors and handle them all, well up to the limit of psBASIC file channels (27 for the professional edition and 3 for the free one). To do this we will leverage the freefile
function.
freefile
returns the lowest available channel number. As we've already seen the file I/O commands accept a variable or function result as a channel #. So we can put these together to open an arbitrary number of files up to the max allowed. I want to read a list of feeds from a file. For simplicity in the program's logic I'll open my list at the maximum channel number to prevent it from altering the value of freefile
as it opens and closes.
So! Here we go:
DEFINT a-z
'*** OPEN our feeds from the list ***
OPEN "i",#27,"feeds.lst"
DO UNTIL eof(27)
LINE INPUT #27,url$
' Trim extraneous white space and escape URL
url$="'"+replace$(trim$(url$),"'","'\''")+"'"
' Run 'rsstail' to watch this feed
OPEN "i",#freefile,"| exec rsstail -N -u "+url$
LOOP
CLOSE #27
WARN: If you are using the free edition you will have to change the 27 to 3, limiting you to two sensors or three if you put the URL list in the program, like in DATA
or OPEN
statements.
The feeds.lst
file is a simple text file with one URL per line, without noise of any kind. Escaping the URL with replace$
is imperative since '&' and other shell related operators are used in URLs. Put simply an URL could fail to open simply because the shell acted on a character we didn't want it to. And, of course, there are the security implications. But I'll assume you aren't hacking yourself.
NOTE: Notice the use of exec
in the OPEN
command? JIC you don't know, most all mechanisms that launch external processes use the default system shell (/bin/sh
) to search the path and implement redirections and other shell logic, which is more or less expected. The shell's exec
command replaces the shell with the program that was requested. Since we don't need the shell to wait around for rsstail
to finish, we use exec
to dismiss it. If you try this program without the exec
command, ps ax
will show you a sh -c ...
process for every rsstail
. exec
simply lightens the load. See your shell's documentation for details.
Now our read event loop can look like this:
DO
FOR f=1 TO freefile-1
' ...
NEXT
LOOP
Putting it together
DEFINT a-z
' "lprint" to marquee's pipe.
OPTION LPRINT "~/.marquee.fifo"
'*** OPEN our feeds from the list ***
OPEN "i",#27,"feeds.lst"
DO UNTIL eof(27)
LINE INPUT #27,url$
' Trim extraneous white space and escape URL
url$="'"+replace$(trim$(url$),"'","'\''")+"'"
' Run 'rsstail' to watch this feed
OPEN "i",#freefile,"| exec rsstail -N -u "+url$
LOOP
CLOSE #27
' *** POLL feeds indefinitely ***
DO
FOR f=1 TO freefile-1
s$=inkey$(0,f)
IF s$<>"" THEN
' Read the title.
LINE INPUT #f,t$
' Prepend our first byte
t$=s$+t$
' Send it to the marquee
LPRINT t$
END IF
NEXT
LOOP
' We will likely loop indefinitely.
NOTE: As I mentioned earlier the LPRINT
stuff is delivering its content to my desktop marquee's input FIFO (pipe). You can replace this with any notification mechanism that suits you, from logging to a physical printer to posting to your twitter account. You just need an appropriate delivery program. The sky's the limit!
rsstail
will check every 15m for new titles and the new ones will be output when they arrive. Depending on site activity it could be days before a new title arrives. This program will run indefinitely waiting for some movement on any of its sensors.
As usual it took very little time to develop this aggregator and very little code. We're leveraging the utility of tools available in the Linux environment to do most of the work. psBASIC makes an excellent glue for pulling it all together.
For more fun, get the "Professional" Edition
It is only $50 for the full "professional" edition, the cost of a good meal out somewhere. Treat yourself to greater levels of productivity with this cool tool.
Read past episodes here
