Automated FM broadcast band signal strength monitoring

These graphs can show when radio propagation conditions for VHF change for various reasons, when new radio stations appear and when maintenance is carried out on existing radio stations. Two USB tuners are in use, one is dedicated to measuring received signal strength. The 87.5-108MHz band is measured once every 2 minutes. The other tuner records 1 minute audio samples and attempts to capture RDS data. The results are stored in a database, this is queried to generate graphs and reports.

The recent arrivals table lists signals that have exceeded the highest signal recorded on the previous day (midnight to midnight), the recent departures table lists signals that have fallen below the previous day's minimum recorded signal strength. The arrivals table is particularly susceptible to logging passing vehicles that are using FM modulators that perhaps don't comply with CE rules on radiated emissions.

I'm considering using a more robust algorithm for both these tables to reduce false readings.

This first graph is the most useful for determining VHF radio conditions, it's an average of the signal strengths on every frequency, including unoccupied frequencies.

This graph shows the mean average signal strength of the entire 87.5 to 108MHz spectrum, sampled in 50kHz steps, summed and divided by the number of steps.

Zoom in by dragging a box over the area of interest, double click to return to full view. Not entirely satisfactory on a touch screen or in Chrome where double click selects the entire graph.

Recent Arrivals
Logged AtFrequencySignalM/S
2017-07-03 00:23:1096.7023.00Mono
2017-07-02 23:07:11100.9044.00Stereo
2017-06-20 06:25:1194.2031.00Mono
2017-06-20 03:09:11100.0043.00Stereo
2017-06-14 19:07:1198.1035.00Mono
Recent Departures
Logged AtFrequencySignal
2017-07-10 23:39:1187.906.00
2017-07-06 01:09:1197.3010.00
2017-06-30 15:59:1188.208.00
2017-06-26 12:57:1187.708.00
2017-06-14 10:15:11104.609.00

Signal strength units very roughly map to decibels but I haven't calibrated it in any way, so all readings are indicative of trends only.

Other graphs are available:

How it works

A Python script runs every 5 minutes, this script tunes a USB FM Radio across a range of frequencies and takes several samples of the received signal strength on each frequency. The highest, lowest and mean average signal strength is then stored in a database.

Database schema:

--
-- Table structure for table `scanitem`
--

CREATE TABLE `scanitem` (
  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `scan` int(11) NOT NULL,
  `frequency` decimal(5,2) NOT NULL,
  `minsignal` decimal(5,2) NOT NULL,
  `maxsignal` decimal(5,2) NOT NULL,
  `meansignal` decimal(5,2) NOT NULL,
  `loggedat` datetime NOT NULL,
  `afcstatus` varchar(1) NOT NULL DEFAULT '0',
  `rxsubchannels` int(1) DEFAULT '0',
  PRIMARY KEY (`pk`),
  KEY `frequency` (`frequency`),
  KEY `scan` (`scan`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

--
-- Table structure for table `scanrange`
--

CREATE TABLE `scanrange` (
  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `startfreq` decimal(5,2) NOT NULL,
  `stopfreq` decimal(5,2) NOT NULL,
  PRIMARY KEY (`pk`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

--
-- Table structure for table `scanrun`
--

CREATE TABLE `scanrun` (
  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `startedat` datetime NOT NULL,
  `completedat` datetime DEFAULT NULL,
  `status` enum('WORKING','COMPLETE','ERROR') DEFAULT NULL,
  `scanrange` int(11) NOT NULL,
  `radioid` int(11) NOT NULL,
  `minmeansignal` decimal(5,2) DEFAULT NULL,
  `maxmeansignal` decimal(5,2) DEFAULT NULL,
  `meanmeansignal` decimal(5,2) DEFAULT NULL,
  PRIMARY KEY (`pk`),
  KEY `scanrange` (`scanrange`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

The Python script uses an FMRadio library that is adapted from pyFMRadio by Martin Grimme which was originally written for Nokia devices, there are just a few tweaks needed to make it work with the more recent v4l API and a beefier version of get_signal_strength than records min/max as well as average