The personal website of Scott W Harden
January 30th, 2010

More Antenna Tinkering

Dental school is taking a lot of time away from me. I try my best to compartmentalize dental school into a chunk of my schedule (a massive chunk), trying to use the rest of the time to spend with my family (wife) and when she's at work work on electronics (which seems to be radio these days). A few weeks ago I took the final amateur radio license exam and received my Amateur Extra license. It's a bunch of technical questions about radio circuitry, antenna theory, and other random stuff. You can see what I mean by taking an online practice test! I applied for a new call sign (extra class operators can get shorter call signs). It seems the FCC gave me a VD. AJ4VD that is! Yes, my old call sign KJ4LDF has gone out the window as I am now AJ4VD! In Morse code, that's .- .--- ....- ...- -..

I made my first Morse code contact from my apartment! This is the radio I'm using. It's a Ten-Tec Century 21 HF CW transceiver which puts out ~30W. I'm using a super-cheap but surprisingly functional homebrew base-loaded vertical antenna. The main vertical element is quarter-inch copper pipe from Home Depot (a couple bucks) cut with 1'' to spare from my 10ft ceiling. Therefore, it's a less-than quarter-wave vertical element, requiring a tuning coil (variable inductor at the base)...

Here you can start to see the tuning coils. Briefly, I scraped a deep gash in the copper pipe such that a big glob of solder would adhere to it, and stuck a wire (yellow, coated) into that solder so it's a good connection to the pipe. I then started wrapping the wire around a few toilet paper rolls [it's all I could find at the time!] adding tap points (regions of exposed wire) every other turn. This functioned somewhat, but didn't allow for fine-tuning (pun intended). I therefore scrapped the bottom half of the cardboard cylinder/coil and constructed a slightly more elegant solution...

That's an Olvaltine container. Yeah, I know, "More chocolaty Olvaltine please!" I used a rotary tool to scrape some measured/templated gashes on each side to give the wire (picture frame hanging wire from Target, 50' for $1.99) something to rest in. It turned out not to be enough, so I hot-glued the wire into the holes. This gives me a lot of exposed wire space to allow me to "tap" the coil wherever I want. By modifying where I clip onto the coil, I modify the length of wire in the coil that's used, therefore modifying the inductance of the coil, allowing for some tuning capabilities. Although it has a narrow tuning range, using the current setup I'm able to get my SWR down to 1:1 on 40m (nice!).

I made a couple of contacts since I got the rig last night. First was K4KOR in central TN, who was calling CQ. I replied (slowly), and he came back to me (blazing fast Morse code). I was unable to copy ANYTHING he said (I'm not that good of an auditory decoder yet!) I'm sure he's incredibly nice and it wasn't intentional, but I had to give up the QSO. I know he copied my call, and I copied his, but I didn't copy ANYTHING else he said. Does that count as my first contact? This morning I fired up the rig at 9:15 and heard W4HAY calling CQ from Northeast TN. I replied, stating that I'm new to CW so go slowly, and he was AMAZINGLY nice at sending me code at a snails pace. I was able to copy 90% of what he said, and will consider him my first solid contact!

Markdown source code last modified on January 18th, 2021
---
title: More Antenna Tinkering
date: 2010-01-30 13:41:59
tags: amateur radio
---

# More Antenna Tinkering

__Dental school is taking a lot of time away from me.__ I try my best to compartmentalize dental school into a chunk of my schedule (a massive chunk), trying to use the rest of the time to spend with my family (wife) and when she's at work work on electronics (which seems to be radio these days). A few weeks ago I took the final amateur radio license exam and received my Amateur Extra license. It's a bunch of technical questions about radio circuitry, antenna theory, and other random stuff. You can see what I mean by taking an [online practice test](http://www.eham.net/exams/)! I applied for a new call sign (extra class operators can get shorter call signs). It seems the FCC gave me a VD. AJ4VD that is! Yes, my old call sign [KJ4LDF](http://www.qrz.com/callsign/KJ4LDF) has gone out the window as I am now [AJ4VD](http://www.qrz.com/callsign/AJ4VD)! In Morse code, that's `.- .--- ....- ...- -..`

<div class="text-center img-border">

[![](Ten_Tec_Century_21_thumb.jpg)](Ten_Tec_Century_21.jpg)

</div>

__I made my first Morse code contact__ from my apartment! This is the radio I'm using. It's a Ten-Tec Century 21 HF CW transceiver which puts out ~30W. I'm using a super-cheap but surprisingly functional homebrew base-loaded vertical antenna. The main vertical element is quarter-inch copper pipe from Home Depot (a couple bucks) cut with 1'' to spare from my 10ft ceiling. Therefore, it's a less-than quarter-wave vertical element, requiring a tuning coil (variable inductor at the base)...

<div class="text-center img-border">

[![](antennaBig_thumb.jpg)](antennaBig.jpg)

</div>

<div class="text-center img-border img-small">

[![](antennaBigger_thumb.jpg)](antennaBigger.jpg)
[![](antennaCoil_thumb.jpg)](antennaCoil.jpg)

</div>

__Here you can start to see the tuning coils.__ Briefly, I scraped a deep gash in the copper pipe such that a big glob of solder would adhere to it, and stuck a wire (yellow, coated) into that solder so it's a good connection to the pipe. I then started wrapping the wire around a few toilet paper rolls \[it's all I could find at the time!\] adding tap points (regions of exposed wire) every other turn. This functioned somewhat, but didn't allow for fine-tuning (pun intended). I therefore scrapped the bottom half of the cardboard cylinder/coil and constructed a slightly more elegant solution...

__That's an [Olvaltine](http://en.wikipedia.org/wiki/Ovaltine) container.__ Yeah, I know, "More chocolaty Olvaltine please!" I used a rotary tool to scrape some measured/templated gashes on each side to give the wire (picture frame hanging wire from Target, 50' for $1.99) something to rest in. It turned out not to be enough, so I hot-glued the wire into the holes. This gives me a lot of exposed wire space to allow me to "tap" the coil wherever I want. By modifying where I clip onto the coil, I modify the length of wire in the coil that's used, therefore modifying the inductance of the coil, allowing for some tuning capabilities. Although it has a narrow tuning range, using the current setup I'm able to get my SWR down to 1:1 on 40m (nice!).

__I made a couple of contacts since I got the rig last night.__ First was [K4KOR](http://www.qrz.com/db/k4kor) in central TN, who was calling CQ. I replied (slowly), and he came back to me (blazing fast Morse code). I was unable to copy ANYTHING he said (I'm not that good of an auditory decoder yet!) I'm sure he's incredibly nice and it wasn't intentional, but I had to give up the QSO. I know he copied my call, and I copied his, but I didn't copy ANYTHING else he said. Does that count as my first contact? This morning I fired up the rig at 9:15 and heard [W4HAY](http://www.qrz.com/db/w4hay) calling CQ from Northeast TN. I replied, stating that I'm new to CW so go slowly, and he was AMAZINGLY nice at sending me code at a snails pace. I was able to copy 90% of what he said, and will consider him my first solid contact!

January 1st, 2010

QRSS and Life in Dental School

QRSS uses extremely simple radio transmitters at extremely low power to send an extremely slow Morse code message over an extremely large distance to extremely sensitive receivers which are extremely dependent on computers to decode. While you might be able to send a voice message across the ocean with ~100 watts of power, there are people sending messages with milliwatts! The main idea is that if you send the signals slow enough, and average the audio data (fast Fourier transformation) over a long enough time, weak signals below the noise threshold will stand out enough to be copied visually.

Without going into more detail than that, this is the kind of stuff I've been copying the last couple days. The image is a slow time-averaged waterfall-type FFT display of 10.140 MHz copied from a Mosley-pro 67 yagi mounted ~180 ft in the air connected to a Kenwood TS-940S transceiver. Red ticks represent 10 seconds. Therefore the frame above is ~10 minutes of audio. The trace on the image is from two different transmitters. The upper trace is from VA3STL's QRSS quarter-watt transmitter from Canada described here and pictured below. The lower trace is from WA5DJJ's QRSS quarter-watt transmitter in New Hampshire, described and pictured here.

I don't know why I'm drawn to QRSS so much. Perhaps it's the fact that it's a hobby which only a handful of people have ever participated in. It uses computers and software, but unlike software-defined radio they don't require complicated equipment, and a QRSS transmitter or receiver can be built from simple and cheap components.

Argo: There's a popular QRSS "grabber" software for Windows called Argo. It dumps out screenshots of itself every few minutes which is nice, but it doesn't assemble them together (which is annoying). I wrote a script to assemble Argo screen captures together as a single image. It's a script for ImageJ.

makeRectangle(13, 94, 560, 320);
run("Crop");
rename("source");
frames = nSlices();
newImage("long", "RGB White", (frames - 1) * 560, 320, 1);
for (i = 0; i < frames; i++)
{
    selectWindow("source");
    setSlice(i + 1);
    run("Select All");
    run("Cut");
    selectWindow("long");
    run("Paste");
    makeRectangle(i * 560, 0, 560, 320);
}
selectWindow("source");
close();

As far as life goes, I'm discovering that it's not the attainment of a goal that gives me pleasure; it's the pursuit of the goal. Perhaps that's why I peruse hobbies which are difficult, and further challenge myself by doing things in weird, quirky ways. I'd love to experiment more with radio, but I don't have much money to spend. Yeah, an all-band 100-watt HF/VHF/UHF rig would be nice, but I don't want to spend hundreds of dollars on that kind of equipment... Maybe when I have a "real" job and stop being a student I'll be in a better place to buy stuff like that. I built a cheap but surprisingly functional base-loaded vertical HF antenna for my apartment balcony (don't worry neighbors, it's taken inside after every use). It's mainly for receive, but I don't see any reason why it couldn't be used for QRP transmitting.

Yes, that's an antenna made from copper pipe, wire, and toilet paper rolls. I wound the wire around the base and created various tap points so it serves as a variable inductor depending on where I gator-clip the radio. Not pictured are 33' radials running inside my apartment serving as grounding. The antenna feeds into a Pixie II direct conversion receiver / QRP transmitter which dumps its output to a laptop computer. I copied some PSK-31 transmissions from Canada with this setup. It works way better than a long / random wire antenna because it dramatically boosts signal-to-noise when tuned to the proper frequency.

UPDATE: VA3STL mentioned me on his site

Markdown source code last modified on January 18th, 2021
---
title: QRSS and Life in Dental School
date: 2010-01-01 18:33:50
tags: qrss, amateur radio
---

# QRSS and Life in Dental School

__QRSS uses extremely simple radio transmitters at extremely low power to send an extremely slow Morse code message over an extremely large distance to extremely sensitive receivers which are extremely dependent on computers to decode.__ While you might be able to send a voice message across the ocean with ~100 watts of power, there are people sending messages with _milliwatts_! The main idea is that if you send the signals slow enough, and average the audio data ([fast Fourier transformation](http://en.wikipedia.org/wiki/Fast_Fourier_transform)) over a long enough time, weak signals below the noise threshold will stand out enough to be copied visually.

<div class="text-center img-border">

[![](qrss_kj4ldf_thumb.jpg)](qrss_kj4ldf.png)

</div>

__Without going into more detail than that, this is the kind of stuff I've been copying the last couple days.__ The image is a slow time-averaged waterfall-type [FFT](http://en.wikipedia.org/wiki/Fast_Fourier_transform) display of 10.140 MHz copied from a [Mosley-pro 67 yagi](http://www.mosley-electronics.com/newspage/pd2ba%20PRO-67-B%205-29-07.jpg) mounted ~180 ft in the air connected to a [Kenwood TS-940S transceiver](http://www.universal-radio.com/USED/UP52lrg.jpg). Red ticks represent 10 seconds. Therefore the frame above is ~10 minutes of audio. The trace on the image is from two different transmitters. The upper trace is from [VA3STL](http://www.qrz.com/callsign?callsign=VA3STL)'s QRSS quarter-watt transmitter from Canada [described here](http://va3stl.wordpress.com/2009/03/09/homebrew-qrss-beacon-working/) and pictured below. The lower trace is from [WA5DJJ](http://www.qrz.com/callsign?callsign=WA5DJJ)'s QRSS quarter-watt transmitter in New Hampshire, [described and pictured here](http://www.zianet.com/dhassall/QRSS.html).

<div class="text-center img-border">

[![](qrss_transmitter_thumb.jpg)](qrss_transmitter.jpg)

</div>

__I don't know why I'm drawn to QRSS so much.__ Perhaps it's the fact that it's a hobby which only a handful of people have ever participated in. It uses computers and software, but unlike [software-defined radio](http://en.wikipedia.org/wiki/Software-defined_radio) they don't require complicated equipment, and a QRSS transmitter or receiver can be built from simple and cheap components.

<div class="text-center img-border">

![](10_01_01_00009.bmp)

</div>

__Argo: __There's a popular QRSS "grabber" software for Windows called [Argo](http://www.sdrham.com/argo/index.html). It dumps out screenshots of itself every few minutes which is nice, but it doesn't assemble them together (which is annoying). I wrote a script to assemble Argo screen captures together as a single image. It's a script for ImageJ.

<div class="text-center img-border">

[![](long_thumb.jpg)](long.jpg)

</div>

```c
makeRectangle(13, 94, 560, 320);
run("Crop");
rename("source");
frames = nSlices();
newImage("long", "RGB White", (frames - 1) * 560, 320, 1);
for (i = 0; i < frames; i++)
{
    selectWindow("source");
    setSlice(i + 1);
    run("Select All");
    run("Cut");
    selectWindow("long");
    run("Paste");
    makeRectangle(i * 560, 0, 560, 320);
}
selectWindow("source");
close();
```

__As far as life goes,__ I'm discovering that it's not the attainment of a goal that gives me pleasure; it's the pursuit of the goal. Perhaps that's why I peruse hobbies which are difficult, and further challenge myself by doing things in weird, quirky ways. I'd love to experiment more with radio, but I don't have much money to spend. Yeah, an all-band 100-watt HF/VHF/UHF rig would be nice, but I don't want to spend hundreds of dollars on that kind of equipment... Maybe when I have a "real" job and stop being a student I'll be in a better place to buy stuff like that. I built a cheap but surprisingly functional base-loaded vertical HF antenna for my apartment balcony (don't worry neighbors, it's taken inside after every use). It's mainly for receive, but I don't see any reason why it couldn't be used for QRP transmitting.

<div class="text-center img-border">

[![](ant_1_thumb.jpg)](ant_1.jpg)

</div>

__Yes, that's an antenna made from copper pipe, wire, and toilet paper rolls. __I wound the wire around the base and created various tap points so it serves as a variable inductor depending on where I gator-clip the radio. Not pictured are 33' radials running inside my apartment serving as grounding. The antenna feeds into a [Pixie II direct conversion receiver / QRP transmitter](http://www.swharden.com/blog/images/pixie2transceiver.gif) which dumps its output to a laptop computer. I copied some [PSK-31](http://en.wikipedia.org/wiki/PSK31) transmissions from Canada with this setup. It works way better than a long / random wire antenna because it dramatically boosts signal-to-noise when tuned to the proper frequency.

<div class="text-center img-border">

[![](ant_2_thumb.jpg)](ant_2.jpg)

</div>

__UPDATE:__ VA3STL [mentioned me on his site](http://va3stl.wordpress.com/2010/01/01/qrss-signal-reaches-florida/)
December 10th, 2009

HF CW on the Cheap

This is a project I intend to work on over Christmas break. I'm wondering if I can make a simple HF transceiver capable of working any HF band by driving a Pixie II CW transceiver with a SI 570 digital programmable oscillator.

Here's what my pixie II sounds like on 80m.mp3 and 40m.mp3 with a strong antenna.

Preliminary work demonstrates a functional receiver powered by a crystal. I don't think the currently-configured digital oscillator is putting out enough power to run the circuit, but it'll take more time to get that up and running. For now, here are some photos of what I've got working and real sound clips of the thing are below. I'm happy with the case I built it in (thanks Ron!), and happy with my level drilling of the holes for power, output, and the CW key!

Briefly, the device is powered by a 9V battery. It's hooked up to an 80m dipole antenna. Output is fed into a computer sound card for amplification / PSK31 analysis.

The internals reveal that it's a simple circuit powered by a single crystal. More crystals are tucked in the case, stuck in foam, for easy transport.

Decoding of PSK31 is happening here, using output from my circuit with the 40m crystal in it.

Markdown source code last modified on January 18th, 2021
---
title: HF CW on the Cheap
date: 2009-12-10 23:44:15
tags: amateur radio, circuit, old
---

# HF CW on the Cheap

__This is a project I intend to work on over Christmas break.__ I'm wondering if I can make a simple HF transceiver capable of working _any HF band_ by driving a [Pixie II CW transceiver](http://www.indianapolis.net/QRPp-I/elmer001/pixie2-schematic.gif) with a [SI 570 digital programmable oscillator](http://www.rfsystem.it/shop/images/si570_view.jpg).

Here's what my pixie II sounds like on [80m.mp3](http://www.SWHarden.com/blog/images/80m.mp3) and [40m.mp3](http://www.SWHarden.com/blog/images/40m.mp3) with a strong antenna.

<div class="text-center img-border">

[![](cw_everything_thumb.jpg)](cw_everything.jpg)
[![](cw_coke_thumb.jpg)](cw_coke.jpg)
[![](cw_close_thumb.jpg)](cw_close.jpg)
[![](cw_open2_thumb.jpg)](cw_open2.jpg)
[![](40mPSK_thumb.jpg)](40mPSK.png)

</div>

__Preliminary work demonstrates a functional receiver__ powered by a crystal. I don't think the currently-configured digital oscillator is putting out enough power to run the circuit, but it'll take more time to get that up and running. For now, here are some photos of what I've got working and real sound clips of the thing are below. I'm happy with the case I built it in (thanks Ron!), and happy with my level drilling of the holes for power, output, and the CW key!

__Briefly, the device__ is powered by a 9V battery. It's hooked up to an 80m dipole antenna. Output is fed into a computer sound card for amplification / PSK31 analysis.

__The internals__ reveal that it's a simple circuit powered by a single crystal. More crystals are tucked in the case, stuck in foam, for easy transport.

__Decoding of PSK31__ is happening here, using output from my circuit with the 40m crystal in it.
June 29th, 2009

Field Day 2009

Last weekend was field day, a disaster simulation / competition for amateur radio operators. In a sentence, people are encouraged to make as many contacts as they can around the world (earning points) using emergency radio preparations (battery and solar powered radios, temporary antennas, etc) for a full 24 hours (2pm to 2pm). I spent the time with the UCF Amateur Radio Club who set up big antennas in a grassy field on campus. It was a fun experience, and the first time I ever got to see a HF rig in operation. A representative for the UCF newspaper showed up, took some interviews, and I ended-up being quoted in the article. I can also be seen in the photo, if you look close enough (yellow square).

Being that amateur radio was something I got into independently (I didn't know anyone else with a license) I was (and still am) very isolated in the hobby. I'm really thankful I found the UCF ARC, even though it wasn't until I'd already been going to UCF for 2 years and was already on my way out. Seeing (and actually get to use) a HF rig was an eye-opening experience for me, and one I'm a little regretful I participated in. Before yesterday, I had already come to terms with my situation (going to dental school in a few weeks and virtually dropping all of my hobbies) and was content with my summer accomplishments so far. My summer goal was to get into radio, and before yesterday I felt I had. I studied for my exam, got my license, learned how to use repeaters on VHF to easily make local contacts, and I was satisfied. I knew HF was out there, and that it allowed communication over thousands of miles, but I ignored it knowing I wouldn't get into it this summer (the equipment is just too expensive for me to justify purchasing). Now, after sitting in front of a rig for several hours, I wish I had the time to upgrade my license, earn a little cash to blow on a HF radio, and spend a few weeks sitting in front of it scouring the waves for random voices around the world. I know it's a little morbid, but I'd probably have to compare the feeling I'm experiencing with what an old person feels like when they realize their end is near and that they won't be able to do the things they always dreamed they would. Oh well, at least I'll be able to fill holes in teeth soon. [smiles convincingly]

After the tents, antennas, and radios were mostly set up, everyone was exhausted. I was ready to make some contacts! I fired-up my 'ol netbook and tried communicating over 40m using psk (a digital mode), a mode I've never used, with software I've never used, on a band I've never used. It wasn't working either. I spent the first several hours in frustration because what I was trying to do wasn't working, and I couldn't figure out why. This photo was taken at the height of my frustration.

Markdown source code last modified on January 18th, 2021
---
title: Field Day 2009
date: 2009-06-29 14:05:32
tags: amateur radio
---

# Field Day 2009

__Last weekend was field day__, a disaster simulation / competition for amateur radio operators. In a sentence, people are encouraged to make as many contacts as they can around the world (earning points) using emergency radio preparations (battery and solar powered radios, temporary antennas, etc) for a full 24 hours (2pm to 2pm). I spent the time with the [UCF Amateur Radio Club](http://www.k4ucf.ucf.edu) who set up [big antennas](http://www2.mmae.ucf.edu/wiki/Image:Fd2008pract_1289.jpg) in a grassy field on campus. It was a fun experience, and the first time I ever got to see a HF rig in operation. A representative for the UCF newspaper showed up, took some interviews, and I ended-up being [quoted in the article](http://www.centralfloridafuture.com/ucf-club-participates-in-ham-radio-field-day-1.1770219). I can also be seen in the photo, if you look close enough (yellow square).

<div class="text-center img-border">

[![](scottpaper_thumb.jpg)](scottpaper.png)

</div>

__Being that amateur radio was something I got into independently (I didn't know anyone else with a license) I was (and still am) very isolated in the hobby.__ I'm really thankful I found the UCF ARC, even though it wasn't until I'd already been going to UCF for 2 years and was already on my way out. Seeing (and actually get to use) a HF rig was an eye-opening experience for me, and one I'm a little regretful I participated in. Before yesterday, I had already come to terms with my situation (going to dental school in a few weeks and virtually dropping all of my hobbies) and was content with my summer accomplishments so far. My summer goal was to get into radio, and before yesterday I felt I had. I studied for my exam, got my license, learned how to use repeaters on VHF to easily make local contacts, and I was satisfied. I knew HF was out there, and that it allowed communication over thousands of miles, but I ignored it knowing I wouldn't get into it this summer (the equipment is just too expensive for me to justify purchasing). Now, after sitting in front of a rig for several hours, I wish I had the time to upgrade my license, earn a little cash to blow on a HF radio, and spend a few weeks sitting in front of it scouring the waves for random voices around the world. I know it's a little morbid, but I'd probably have to compare the feeling I'm experiencing with what an old person feels like when they realize their end is near and that they won't be able to do the things they always dreamed they would. Oh well, at least I'll be able to fill holes in teeth soon. \[smiles convincingly\]

<div class="text-center img-border">

[![](me_thumb.jpg)](me.png)

</div>

After the tents, antennas, and radios were mostly set up, everyone was exhausted. I was ready to make some contacts! I fired-up my 'ol netbook and tried communicating over 40m using psk (a digital mode), a mode I've never used, with software I've never used, on a band I've never used. It wasn't working either. I spent the first _several_ hours in frustration because what I was trying to do wasn't working, and I couldn't figure out why. This photo was taken at the height of my frustration.

June 18th, 2009

pySquelch - Frequency Activity Reports via Python

Update: this project is now on GitHub https://github.com/FredEckert/pySquelch

I've been working on the pySquelch project which is basically a method to graph frequency usage with respect to time. The code I'm sharing below listens to the microphone jack on the sound card (hooked up to a radio) and determines when transmissions begin and end. I ran the code below for 24 hours and this is the result:

This graph represents frequency activity with respect to time. The semi-transparent gray line represents the raw frequency usage in fractional minutes the frequency was tied-up by transmissions. The solid blue line represents the same data but smoothed by 10 minutes (in both directions) by the Gaussian smoothing method modified slightly from my linear data smoothing with Python page.

I used the code below to generate the log, and the code further below to create the graph from the log file. Assuming your microphone is enabled and everything else is working, this software will require you to determine your own threshold for talking vs. no talking. Read the code and you'll figure out how test your sound card settings.

If you want to try this yourself you need a Linux system (a Windows system version could be created simply by replacing getVolEach() with a Windows-based audio level detection system) with Python and the alsaaudio, numpy, and matplotlib libraries. Try running the code on your own, and if it doesn't recognize a library "aptitude search" for it. Everything you need can be installed from packages in the common repository.


# pySquelchLogger.py
import time
import random
import alsaaudio
import audioop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK)
inp.setchannels(2)
inp.setrate(1000)
inp.setformat(alsaaudio.PCM_FORMAT_S8)
inp.setperiodsize(100)
addToLog = ""
lastLogTime = 0

testLevel = False  # SET THIS TO 'True' TO TEST YOUR SOUNDCARD


def getVolEach():
    # this is a quick way to detect activity.
    # modify this function to use alternate methods of detection.
    while True:
        l, data = inp.read()  # poll the audio device
        if l > 0:
            break
    vol = audioop.max(data, 1)  # get the maximum amplitude
    if testLevel:
        print vol
    if vol > 10:
        return True  # SET THIS NUMBER TO SUIT YOUR NEEDS ###
    return False


def getVol():
    # reliably detect activity by getting 3 consistant readings.
    a, b, c = True, False, False
    while True:
        a = getVolEach()
        b = getVolEach()
        c = getVolEach()
        if a == b == c:
            if testLevel:
                print "RESULT:", a
            break
    if a == True:
        time.sleep(1)
    return a


def updateLog():
    # open the log file, append the new data, and save it again.
    global addToLog, lastLogTime
    # print "UPDATING LOG"
    if len(addToLog) > 0:
        f = open('log.txt', 'a')
        f.write(addToLog)
        f.close()
        addToLog = ""
    lastLogTime = time.mktime(time.localtime())


def findSquelch():
    # this will record a single transmission and store its data.
    global addToLog
    while True:  # loop until we hear talking
        time.sleep(.5)
        if getVol() == True:
            start = time.mktime(time.localtime())
            print start,
            break
    while True:  # loop until talking stops
        time.sleep(.1)
        if getVol() == False:
            length = time.mktime(time.localtime())-start
            print length
            break
    newLine = "%d,%d " % (start, length)
    addToLog += newLine
    if start-lastLogTime > 30:
        updateLog()  # update the log


while True:
    findSquelch()

The logging code (above) produces a log file like this (below). The values represent the start time of each transmission (in seconds since epoch) followed by the duration of the transmission.

#log.txt
1245300044,5 1245300057,4 1245300063,16 1245300094,13 1245300113,4 1245300120,14 1245300195,4 1245300295,4 1245300348,4 1245300697,7 1245300924,3 1245301157,4 1245301207,12 1245301563,4 1245302104,6 1245302114,6 1245302192,3 1245302349,4 1245302820,4 1245304812,13 1245308364,10 1245308413,14 1245312008,14 1245313953,11 1245314008,6 1245314584,4 1245314641,3 1245315212,5 1245315504,6 1245315604,13 1245315852,3 1245316255,6 1245316480,5 1245316803,3 1245316839,6 1245316848,11 1245316867,5 1245316875,12 1245316893,13 1245316912,59 1245316974,12 1245316988,21 1245317011,17 1245317044,10 1245317060,6 1245317071,7 1245317098,33 1245317140,96 1245317241,15 1245317259,14 1245317277,8 1245317298,18 1245317322,103 1245317435,40 1245317488,18 1245317508,34 1245317560,92 1245317658,29 1245317697,55 1245317755,33 1245317812,5 1245317818,7 1245317841,9 1245317865,25 1245317892,79 1245317972,30 1245318007,8 1245318021,60 1245318083,28 1245318114,23 1245318140,25 1245318167,341 1245318512,154 1245318670,160 1245318834,22 1245318859,9 1245318870,162 1245319042,57 1245319102,19 1245319123,30 1245319154,18 1245319206,5 1245319214,13 1245319229,6 1245319238,6 1245319331,9 1245319341,50 1245319397,71 1245319470,25 1245319497,40 1245319540,8 1245319551,77 1245319629,4 1245319638,36 1245319677,158 1245319837,25 1245319865,40 1245319907,33 1245319948,92 1245320043,26 1245320100,9 1245320111,34 1245320146,8 1245320159,6 1245320167,8 1245320181,12 1245320195,15 1245320212,14 1245320238,18 1245320263,46 1245320310,9 1245320326,22 1245320352,27 1245320381,15 1245320398,24 1245320425,57 1245320483,16 1245320501,40 1245320543,43 1245320589,65 1245320657,63 1245320722,129 1245320853,33 1245320889,50 1245320940,1485 1245322801,7 1245322809,103 1245322923,5 1245322929,66 1245323553,4 1245324203,15 1245324383,5 1245324570,7 1245324835,4 1245325200,8 1245325463,5 1245326414,12 1245327340,12 1245327836,4 1245327973,4 1245330006,12 1245331244,11 1245331938,11 1245332180,5 1245332187,81 1245332573,5 1245333609,12 1245334447,10 1245334924,9 1245334945,4 1245334971,4 1245335031,9 1245335076,11 1245335948,16 1245335965,27 1245335993,113 1245336107,79 1245336187,64 1245336253,37 1245336431,4 1245336588,5 1245336759,7 1245337048,3 1245337206,13 1245337228,4 1245337309,4 1245337486,6 1245337536,8 1245337565,38 1245337608,100 1245337713,25 1245337755,169 1245337930,8 1245337941,20 1245337967,6 1245337978,7 1245337996,20 1245338019,38 1245338060,127 1245338192,30 1245338227,22 1245338250,15 1245338272,15 1245338310,3 1245338508,4 1245338990,5 1245339136,5 1245339489,8 1245339765,4 1245340220,5 1245340233,6 1245340266,10 1245340278,22 1245340307,7 1245340315,28 1245340359,32 1245340395,4 1245340403,41 1245340446,46 1245340494,58 1245340554,17 1245340573,21 1245340599,3 1245340604,5 1245340611,46 1245340661,26 1245340747,4 1245340814,14 1245341043,4 1245341104,4 1245341672,4 1245341896,5 1245341906,3 1245342301,3 1245342649,6 1245342884,5 1245342929,4 1245343314,6 1245343324,10 1245343335,16 1245343353,39 1245343394,43 1245343439,62 1245343561,3 1245343790,4 1245344115,3 1245344189,5 1245344233,4 1245344241,6 1245344408,12 1245344829,3 1245345090,5 1245345457,5 1245345689,4 1245346086,3 1245347112,12 1245348006,14 1245348261,10 1245348873,4 1245348892,3 1245350303,11 1245350355,4 1245350766,5 1245350931,3 1245351605,14 1245351673,55 1245351729,23 1245351754,5 1245352123,37 1245352163,21 1245352186,18 1245352209,40 1245352251,49 1245352305,8 1245352315,5 1245352321,6 1245352329,22 1245352353,48 1245352404,77 1245352483,58 1245352543,17 1245352570,19 1245352635,5 1245352879,3 1245352899,5 1245352954,4 1245352962,6 1245352970,58 1245353031,21 1245353055,14 1245353071,52 1245353131,37 1245353170,201 1245353373,56 1245353431,18 1245353454,47 1245353502,13 1245353519,106 1245353627,10 1245353647,12 1245353660,30 1245353699,42 1245353746,28 1245353776,29 1245353806,9 1245353818,21 1245353841,10 1245353853,6 1245353862,224 1245354226,4 1245354964,63 1245355029,4 1245355036,142 1245355180,148 1245355330,7 1245355338,23 1245355363,9 1245355374,60 1245355437,142 1245355581,27 1245355609,5 1245355615,2 1245355630,64 1245355700,7 1245355709,73 1245355785,45 1245355834,85 1245355925,9 1245356234,5 1245356620,6 1245356629,12 1245356643,29 1245356676,120 1245356798,126 1245356937,62 1245357001,195 1245357210,17 1245357237,15 1245357258,24 1245357284,53 1245357339,2 1245357345,27 1245357374,76 1245357452,28 1245357482,42 1245357529,14 1245357545,35 1245357582,74 1245357661,30 1245357693,19 1245357714,38 1245357758,11 1245357777,37 1245357817,49 1245357868,19 1245357891,31 1245357931,48 1245357990,49 1245358043,24 1245358082,22 1245358108,17 1245358148,18 1245358168,7 1245358179,6 1245358186,19 1245358209,17 1245358229,5 1245358240,9 1245358252,10 1245358263,6 1245358272,9 1245358296,26 1245358328,49 1245358381,6 1245358389,38 1245358453,19 1245358476,24 1245358504,21 1245358533,76 1245358628,24 1245358653,10 1245358669,105 1245358781,20 1245358808,14 1245358836,6 1245358871,61 1245358933,0 1245358936,44 1245358982,11 1245358996,25 1245359023,15 1245359040,32 1245359076,19 1245359099,13 1245359117,16 1245359138,12 1245359161,33 1245359215,32 1245359249,14 1245359272,7 1245359314,10 1245359333,36 1245359371,21 1245359424,10 1245359447,61 1245359514,32 1245359560,42 1245359604,87 1245359700,60 1245359762,23 1245359786,4 1245359791,8 1245359803,6 1245359813,107 1245359922,29 1245359953,22 1245359978,86 1245360069,75 1245360147,22 1245360170,0 1245360184,41 1245360239,15 1245360256,34 1245360301,37 1245360339,1 1245360342,28 1245360372,20 1245360394,32 1245360440,24 1245360526,3 1245360728,3 1245361011,4 1245361026,35 1245361064,137 1245361359,5 1245362172,11 1245362225,21 1245362248,51 1245362302,20 1245362334,42 1245362418,12 1245362468,7 1245362557,9 1245362817,3 1245363175,4 1245363271,4 1245363446,3 1245363539,4 1245363573,4 1245363635,1 1245363637,3 1245363740,5 1245363875,3 1245364075,4 1245364354,14 1245364370,19 1245364391,49 1245364442,34 1245364478,23 1245364502,80 1245364633,15 1245364650,8 1245364673,16 1245364691,47 1245364739,53 1245364795,39 1245364836,25 1245365353,4 1245365640,11 1245365665,5 1245365726,8 1245365778,7 1245365982,4 1245366017,13 1245366042,6 1245366487,4 1245366493,4 1245366500,4 1245366507,3 1245366622,5 1245366690,5 1245366946,4 1245366953,16 1245366975,8 1245366996,7 1245367005,7 1245367031,6 1245367040,9 1245367051,7 1245367059,23 1245367084,76 1245367166,158 1245367740,4 1245367804,3 1245367847,4 1245367887,9 1245369300,10 1245369611,12 1245370038,10 1245370374,8 1245370668,5 1245370883,5 1245370927,7 1245370945,9 1245370961,16 1245370978,414 1245371398,135 1245371535,252 1245371791,238 1245372034,199 1245372621,4 1245372890,5 1245373043,7 1245373060,9 1245373073,6 1245373081,68 1245373151,10 1245373162,49 1245373212,79 1245373300,12 1245373313,38 1245373353,20 1245373374,59 1245373435,28 1245373465,94 1245373560,11 1245373574,53 1245373629,22 1245373654,6 1245373662,334 1245373998,169 1245374176,41 1245374219,26 1245374246,51 1245374299,31 1245374332,57 1245374391,55 1245374535,4 1245374759,7 1245374769,200 1245374971,215 1245375188,181 1245375371,81 1245375455,59 1245375516,33 1245375552,19 1245375572,56 1245375629,220 1245375850,32 1245375884,26 1245375948,7 1245375964,114 1245376473,4 1245376810,13 1245378296,10 1245378950,12 1245379004,3 1245379569,4 1245379582,4 1245379615,6 1245380030,3 1245380211,4 1245380412,14 1245380727,4 1245380850,4

This log file is only 7.3 KB. At this rate, a years' worth of log data can be stored in less than 3MB of plain text files. The data presented here can be graphed (producing the image at the top of the page) using the following code:

# pySquelchGrapher.py
import numpy
import datetime
import pylab
print "loading libraries...",
print "complete"


def loadData(fname="log.txt"):
    print "loading data...",
    # load signal/duration from log file
    f = open(fname)
    raw = f.read()
    f.close()
    raw = raw.replace('n', ' ')
    raw = raw.split(" ")
    signals = []
    for line in raw:
        if len(line) < 3:
            continue
        line = line.split(',')
        sec = datetime.datetime.fromtimestamp(int(line[0]))
        dur = int(line[1])
        signals.append([sec, dur])
    print "complete"
    return signals


def findDays(signals):
    # determine which days are in the log file
    print "finding days...",
    days = []
    for signal in signals:
        day = signal[0].date()
        if not day in days:
            days.append(day)
    print "complete"
    return days


def genMins(day):
    # generate an array for every minute in a certain day
    print "generating bins...",
    mins = []
    startTime = datetime.datetime(day.year, day.month, day.day)
    minute = datetime.timedelta(minutes=1)
    for i in xrange(60*60):
        mins.append(startTime+minute*i)
    print "complete"
    return mins


def fillMins(mins, signals):
    print "filling bins...",
    vals = [0]*len(mins)
    dayToDo = signals[0][0].date()
    for signal in signals:
        if not signal[0].date() == dayToDo:
            continue
        sec = signal[0]
        dur = signal[1]
        prebuf = sec.second
        minOfDay = sec.hour*60+sec.minute
        if dur+prebuf < 60:  # simple case, no rollover seconds
            vals[minOfDay] = dur
        else:  # if duration exceeds the minute the signal started in
            vals[minOfDay] = 60-prebuf
            dur = dur+prebuf
            while (dur > 0):  # add rollover seconds to subsequent minutes
                minOfDay += 1
                dur = dur-60
                if dur <= 0:
                    break
                if dur >= 60:
                    vals[minOfDay] = 60
                else:
                    vals[minOfDay] = dur
    print "complete"
    return vals


def normalize(vals):
    print "normalizing data...",
    divBy = float(max(vals))
    for i in xrange(len(vals)):
        vals[i] = vals[i]/divBy
    print "complete"
    return vals


def smoothListGaussian(list, degree=10):
    print "smoothing...",
    window = degree*2-1
    weight = numpy.array([1.0]*window)
    weightGauss = []
    for i in range(window):
        i = i-degree+1
        frac = i/float(window)
        gauss = 1/(numpy.exp((4*(frac))**2))
        weightGauss.append(gauss)
    weight = numpy.array(weightGauss)*weight
    smoothed = [0.0]*(len(list)-window)
    for i in range(len(smoothed)):
        smoothed[i] = sum(numpy.array(list[i:i+window])*weight)/sum(weight)
    while len(list) > len(smoothed)+int(window/2):
        smoothed.insert(0, smoothed[0])
    while len(list) > len(smoothed):
        smoothed.append(smoothed[0])
    print "complete"
    return smoothed


signals = loadData()
days = findDays(signals)
for day in days:
    mins = genMins(day)
    vals = normalize(fillMins(mins, signals))
    fig = pylab.figure()
    pylab.grid(alpha=.2)
    pylab.plot(mins, vals, 'k', alpha=.1)
    pylab.plot(mins, smoothListGaussian(vals), 'b', lw=1)
    pylab.axis([day, day+datetime.timedelta(days=1), None, None])
    fig.autofmt_xdate()
    pylab.title("147.120 MHz Usage for "+str(day))
    pylab.xlabel("time of day")
    pylab.ylabel("fractional usage")
    pylab.show()
Markdown source code last modified on January 18th, 2021
---
title: pySquelch - Frequency Activity Reports via Python
date: 2009-06-18 22:59:01
tags: amateur radio, python, old
---

# pySquelch - Frequency Activity Reports via Python

<p class="has-background has-light-green-cyan-background-color"><strong>Update:</strong> this project is now on GitHub  <a href="https://github.com/FredEckert/pySquelch">https://github.com/FredEckert/pySquelch</a> </p>

__I've been working on the pySquelch project__ which is basically a method to graph frequency usage with respect to time. The code I'm sharing below listens to the microphone jack on the sound card (hooked up to a radio) and determines when transmissions begin and end. I ran the code below for 24 hours and this is the result:

<div class="text-center img-border">

[![](1png_thumb.jpg)](1png.png)

</div>

__This graph represents frequency activity with respect to time. __The semi-transparent gray line represents the raw frequency usage in fractional minutes the frequency was tied-up by transmissions. The solid blue line represents the same data but smoothed by 10 minutes (in both directions) by the Gaussian smoothing method modified slightly from my [linear data smoothing with Python page](http://www.swharden.com/blog/2008-11-17-linear-data-smoothing-in-python/).

<div class="text-center img-border">

[![](2png_thumb.jpg)](2png.png)

</div>

__I used the code below to generate the log, and the code further below to create the graph from the log file.__ Assuming your microphone is enabled and everything else is working, this software will require you to determine your own threshold for talking vs. no talking. Read the code and you'll figure out how test your sound card settings.

__If you want to try this yourself__ you need a Linux system (a Windows system version could be created simply by replacing _getVolEach()_ with a Windows-based audio level detection system) with Python and the alsaaudio, numpy, and matplotlib libraries. Try running the code on your own, and if it doesn't recognize a library "aptitude search" for it. Everything you need can be installed from packages in the common repository.

```python

# pySquelchLogger.py
import time
import random
import alsaaudio
import audioop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK)
inp.setchannels(2)
inp.setrate(1000)
inp.setformat(alsaaudio.PCM_FORMAT_S8)
inp.setperiodsize(100)
addToLog = ""
lastLogTime = 0

testLevel = False  # SET THIS TO 'True' TO TEST YOUR SOUNDCARD


def getVolEach():
    # this is a quick way to detect activity.
    # modify this function to use alternate methods of detection.
    while True:
        l, data = inp.read()  # poll the audio device
        if l > 0:
            break
    vol = audioop.max(data, 1)  # get the maximum amplitude
    if testLevel:
        print vol
    if vol > 10:
        return True  # SET THIS NUMBER TO SUIT YOUR NEEDS ###
    return False


def getVol():
    # reliably detect activity by getting 3 consistant readings.
    a, b, c = True, False, False
    while True:
        a = getVolEach()
        b = getVolEach()
        c = getVolEach()
        if a == b == c:
            if testLevel:
                print "RESULT:", a
            break
    if a == True:
        time.sleep(1)
    return a


def updateLog():
    # open the log file, append the new data, and save it again.
    global addToLog, lastLogTime
    # print "UPDATING LOG"
    if len(addToLog) > 0:
        f = open('log.txt', 'a')
        f.write(addToLog)
        f.close()
        addToLog = ""
    lastLogTime = time.mktime(time.localtime())


def findSquelch():
    # this will record a single transmission and store its data.
    global addToLog
    while True:  # loop until we hear talking
        time.sleep(.5)
        if getVol() == True:
            start = time.mktime(time.localtime())
            print start,
            break
    while True:  # loop until talking stops
        time.sleep(.1)
        if getVol() == False:
            length = time.mktime(time.localtime())-start
            print length
            break
    newLine = "%d,%d " % (start, length)
    addToLog += newLine
    if start-lastLogTime > 30:
        updateLog()  # update the log


while True:
    findSquelch()
```

__The logging code (above) produces a log file like this (below).__ The values represent the start time of each transmission (in [seconds since epoch](http://en.wikipedia.org/wiki/Unix_time)) followed by the duration of the transmission.

```
#log.txt
1245300044,5 1245300057,4 1245300063,16 1245300094,13 1245300113,4 1245300120,14 1245300195,4 1245300295,4 1245300348,4 1245300697,7 1245300924,3 1245301157,4 1245301207,12 1245301563,4 1245302104,6 1245302114,6 1245302192,3 1245302349,4 1245302820,4 1245304812,13 1245308364,10 1245308413,14 1245312008,14 1245313953,11 1245314008,6 1245314584,4 1245314641,3 1245315212,5 1245315504,6 1245315604,13 1245315852,3 1245316255,6 1245316480,5 1245316803,3 1245316839,6 1245316848,11 1245316867,5 1245316875,12 1245316893,13 1245316912,59 1245316974,12 1245316988,21 1245317011,17 1245317044,10 1245317060,6 1245317071,7 1245317098,33 1245317140,96 1245317241,15 1245317259,14 1245317277,8 1245317298,18 1245317322,103 1245317435,40 1245317488,18 1245317508,34 1245317560,92 1245317658,29 1245317697,55 1245317755,33 1245317812,5 1245317818,7 1245317841,9 1245317865,25 1245317892,79 1245317972,30 1245318007,8 1245318021,60 1245318083,28 1245318114,23 1245318140,25 1245318167,341 1245318512,154 1245318670,160 1245318834,22 1245318859,9 1245318870,162 1245319042,57 1245319102,19 1245319123,30 1245319154,18 1245319206,5 1245319214,13 1245319229,6 1245319238,6 1245319331,9 1245319341,50 1245319397,71 1245319470,25 1245319497,40 1245319540,8 1245319551,77 1245319629,4 1245319638,36 1245319677,158 1245319837,25 1245319865,40 1245319907,33 1245319948,92 1245320043,26 1245320100,9 1245320111,34 1245320146,8 1245320159,6 1245320167,8 1245320181,12 1245320195,15 1245320212,14 1245320238,18 1245320263,46 1245320310,9 1245320326,22 1245320352,27 1245320381,15 1245320398,24 1245320425,57 1245320483,16 1245320501,40 1245320543,43 1245320589,65 1245320657,63 1245320722,129 1245320853,33 1245320889,50 1245320940,1485 1245322801,7 1245322809,103 1245322923,5 1245322929,66 1245323553,4 1245324203,15 1245324383,5 1245324570,7 1245324835,4 1245325200,8 1245325463,5 1245326414,12 1245327340,12 1245327836,4 1245327973,4 1245330006,12 1245331244,11 1245331938,11 1245332180,5 1245332187,81 1245332573,5 1245333609,12 1245334447,10 1245334924,9 1245334945,4 1245334971,4 1245335031,9 1245335076,11 1245335948,16 1245335965,27 1245335993,113 1245336107,79 1245336187,64 1245336253,37 1245336431,4 1245336588,5 1245336759,7 1245337048,3 1245337206,13 1245337228,4 1245337309,4 1245337486,6 1245337536,8 1245337565,38 1245337608,100 1245337713,25 1245337755,169 1245337930,8 1245337941,20 1245337967,6 1245337978,7 1245337996,20 1245338019,38 1245338060,127 1245338192,30 1245338227,22 1245338250,15 1245338272,15 1245338310,3 1245338508,4 1245338990,5 1245339136,5 1245339489,8 1245339765,4 1245340220,5 1245340233,6 1245340266,10 1245340278,22 1245340307,7 1245340315,28 1245340359,32 1245340395,4 1245340403,41 1245340446,46 1245340494,58 1245340554,17 1245340573,21 1245340599,3 1245340604,5 1245340611,46 1245340661,26 1245340747,4 1245340814,14 1245341043,4 1245341104,4 1245341672,4 1245341896,5 1245341906,3 1245342301,3 1245342649,6 1245342884,5 1245342929,4 1245343314,6 1245343324,10 1245343335,16 1245343353,39 1245343394,43 1245343439,62 1245343561,3 1245343790,4 1245344115,3 1245344189,5 1245344233,4 1245344241,6 1245344408,12 1245344829,3 1245345090,5 1245345457,5 1245345689,4 1245346086,3 1245347112,12 1245348006,14 1245348261,10 1245348873,4 1245348892,3 1245350303,11 1245350355,4 1245350766,5 1245350931,3 1245351605,14 1245351673,55 1245351729,23 1245351754,5 1245352123,37 1245352163,21 1245352186,18 1245352209,40 1245352251,49 1245352305,8 1245352315,5 1245352321,6 1245352329,22 1245352353,48 1245352404,77 1245352483,58 1245352543,17 1245352570,19 1245352635,5 1245352879,3 1245352899,5 1245352954,4 1245352962,6 1245352970,58 1245353031,21 1245353055,14 1245353071,52 1245353131,37 1245353170,201 1245353373,56 1245353431,18 1245353454,47 1245353502,13 1245353519,106 1245353627,10 1245353647,12 1245353660,30 1245353699,42 1245353746,28 1245353776,29 1245353806,9 1245353818,21 1245353841,10 1245353853,6 1245353862,224 1245354226,4 1245354964,63 1245355029,4 1245355036,142 1245355180,148 1245355330,7 1245355338,23 1245355363,9 1245355374,60 1245355437,142 1245355581,27 1245355609,5 1245355615,2 1245355630,64 1245355700,7 1245355709,73 1245355785,45 1245355834,85 1245355925,9 1245356234,5 1245356620,6 1245356629,12 1245356643,29 1245356676,120 1245356798,126 1245356937,62 1245357001,195 1245357210,17 1245357237,15 1245357258,24 1245357284,53 1245357339,2 1245357345,27 1245357374,76 1245357452,28 1245357482,42 1245357529,14 1245357545,35 1245357582,74 1245357661,30 1245357693,19 1245357714,38 1245357758,11 1245357777,37 1245357817,49 1245357868,19 1245357891,31 1245357931,48 1245357990,49 1245358043,24 1245358082,22 1245358108,17 1245358148,18 1245358168,7 1245358179,6 1245358186,19 1245358209,17 1245358229,5 1245358240,9 1245358252,10 1245358263,6 1245358272,9 1245358296,26 1245358328,49 1245358381,6 1245358389,38 1245358453,19 1245358476,24 1245358504,21 1245358533,76 1245358628,24 1245358653,10 1245358669,105 1245358781,20 1245358808,14 1245358836,6 1245358871,61 1245358933,0 1245358936,44 1245358982,11 1245358996,25 1245359023,15 1245359040,32 1245359076,19 1245359099,13 1245359117,16 1245359138,12 1245359161,33 1245359215,32 1245359249,14 1245359272,7 1245359314,10 1245359333,36 1245359371,21 1245359424,10 1245359447,61 1245359514,32 1245359560,42 1245359604,87 1245359700,60 1245359762,23 1245359786,4 1245359791,8 1245359803,6 1245359813,107 1245359922,29 1245359953,22 1245359978,86 1245360069,75 1245360147,22 1245360170,0 1245360184,41 1245360239,15 1245360256,34 1245360301,37 1245360339,1 1245360342,28 1245360372,20 1245360394,32 1245360440,24 1245360526,3 1245360728,3 1245361011,4 1245361026,35 1245361064,137 1245361359,5 1245362172,11 1245362225,21 1245362248,51 1245362302,20 1245362334,42 1245362418,12 1245362468,7 1245362557,9 1245362817,3 1245363175,4 1245363271,4 1245363446,3 1245363539,4 1245363573,4 1245363635,1 1245363637,3 1245363740,5 1245363875,3 1245364075,4 1245364354,14 1245364370,19 1245364391,49 1245364442,34 1245364478,23 1245364502,80 1245364633,15 1245364650,8 1245364673,16 1245364691,47 1245364739,53 1245364795,39 1245364836,25 1245365353,4 1245365640,11 1245365665,5 1245365726,8 1245365778,7 1245365982,4 1245366017,13 1245366042,6 1245366487,4 1245366493,4 1245366500,4 1245366507,3 1245366622,5 1245366690,5 1245366946,4 1245366953,16 1245366975,8 1245366996,7 1245367005,7 1245367031,6 1245367040,9 1245367051,7 1245367059,23 1245367084,76 1245367166,158 1245367740,4 1245367804,3 1245367847,4 1245367887,9 1245369300,10 1245369611,12 1245370038,10 1245370374,8 1245370668,5 1245370883,5 1245370927,7 1245370945,9 1245370961,16 1245370978,414 1245371398,135 1245371535,252 1245371791,238 1245372034,199 1245372621,4 1245372890,5 1245373043,7 1245373060,9 1245373073,6 1245373081,68 1245373151,10 1245373162,49 1245373212,79 1245373300,12 1245373313,38 1245373353,20 1245373374,59 1245373435,28 1245373465,94 1245373560,11 1245373574,53 1245373629,22 1245373654,6 1245373662,334 1245373998,169 1245374176,41 1245374219,26 1245374246,51 1245374299,31 1245374332,57 1245374391,55 1245374535,4 1245374759,7 1245374769,200 1245374971,215 1245375188,181 1245375371,81 1245375455,59 1245375516,33 1245375552,19 1245375572,56 1245375629,220 1245375850,32 1245375884,26 1245375948,7 1245375964,114 1245376473,4 1245376810,13 1245378296,10 1245378950,12 1245379004,3 1245379569,4 1245379582,4 1245379615,6 1245380030,3 1245380211,4 1245380412,14 1245380727,4 1245380850,4
```

__This log file__ is only 7.3 KB. At this rate, a years' worth of log data can be stored in less than 3MB of plain text files. The data presented here can be graphed (producing the image at the top of the page) using the following code:

```python
# pySquelchGrapher.py
import numpy
import datetime
import pylab
print "loading libraries...",
print "complete"


def loadData(fname="log.txt"):
    print "loading data...",
    # load signal/duration from log file
    f = open(fname)
    raw = f.read()
    f.close()
    raw = raw.replace('n', ' ')
    raw = raw.split(" ")
    signals = []
    for line in raw:
        if len(line) < 3:
            continue
        line = line.split(',')
        sec = datetime.datetime.fromtimestamp(int(line[0]))
        dur = int(line[1])
        signals.append([sec, dur])
    print "complete"
    return signals


def findDays(signals):
    # determine which days are in the log file
    print "finding days...",
    days = []
    for signal in signals:
        day = signal[0].date()
        if not day in days:
            days.append(day)
    print "complete"
    return days


def genMins(day):
    # generate an array for every minute in a certain day
    print "generating bins...",
    mins = []
    startTime = datetime.datetime(day.year, day.month, day.day)
    minute = datetime.timedelta(minutes=1)
    for i in xrange(60*60):
        mins.append(startTime+minute*i)
    print "complete"
    return mins


def fillMins(mins, signals):
    print "filling bins...",
    vals = [0]*len(mins)
    dayToDo = signals[0][0].date()
    for signal in signals:
        if not signal[0].date() == dayToDo:
            continue
        sec = signal[0]
        dur = signal[1]
        prebuf = sec.second
        minOfDay = sec.hour*60+sec.minute
        if dur+prebuf < 60:  # simple case, no rollover seconds
            vals[minOfDay] = dur
        else:  # if duration exceeds the minute the signal started in
            vals[minOfDay] = 60-prebuf
            dur = dur+prebuf
            while (dur > 0):  # add rollover seconds to subsequent minutes
                minOfDay += 1
                dur = dur-60
                if dur <= 0:
                    break
                if dur >= 60:
                    vals[minOfDay] = 60
                else:
                    vals[minOfDay] = dur
    print "complete"
    return vals


def normalize(vals):
    print "normalizing data...",
    divBy = float(max(vals))
    for i in xrange(len(vals)):
        vals[i] = vals[i]/divBy
    print "complete"
    return vals


def smoothListGaussian(list, degree=10):
    print "smoothing...",
    window = degree*2-1
    weight = numpy.array([1.0]*window)
    weightGauss = []
    for i in range(window):
        i = i-degree+1
        frac = i/float(window)
        gauss = 1/(numpy.exp((4*(frac))**2))
        weightGauss.append(gauss)
    weight = numpy.array(weightGauss)*weight
    smoothed = [0.0]*(len(list)-window)
    for i in range(len(smoothed)):
        smoothed[i] = sum(numpy.array(list[i:i+window])*weight)/sum(weight)
    while len(list) > len(smoothed)+int(window/2):
        smoothed.insert(0, smoothed[0])
    while len(list) > len(smoothed):
        smoothed.append(smoothed[0])
    print "complete"
    return smoothed


signals = loadData()
days = findDays(signals)
for day in days:
    mins = genMins(day)
    vals = normalize(fillMins(mins, signals))
    fig = pylab.figure()
    pylab.grid(alpha=.2)
    pylab.plot(mins, vals, 'k', alpha=.1)
    pylab.plot(mins, smoothListGaussian(vals), 'b', lw=1)
    pylab.axis([day, day+datetime.timedelta(days=1), None, None])
    fig.autofmt_xdate()
    pylab.title("147.120 MHz Usage for "+str(day))
    pylab.xlabel("time of day")
    pylab.ylabel("fractional usage")
    pylab.show()

```

Pages