7:47:50 pm on 3/14/10
Menu
» Home
» About Scott
» QRSS VD
» Old Stuff
» Archive
» Contact

Categories
» C/C++
» Circuitry
» Dentistry
» DIY ECG
» General
» Linux
» Microcontrollers
» Molecular Biology
» My Website
» PHP
» Prime Numbers
» Python
» Radio
» UCF Lab
» Everything
Writings
» MD Labels
» Streamrip
» AIM Thoughts
» WindowsXP?
» Partitioning
» CD/DVD Repair
» Monitor Info
» CRT Deflection
» Venomcrack
» Flash Thing
» Heart/Brain
» Diabetes
» Triops
» Biomed

Friends
» Fred
» Kyle W
» Nick
» Louis
» Tom
» Kyle H




Archives
» March 2010
» February 2010
» January 2010
» December 2009
» September 2009
» August 2009
» July 2009
» June 2009
» May 2009
» April 2009
» March 2009
» February 2009
» January 2009
» December 2008
» November 2008
» October 2008
» September 2008
» September 2007
» December 2006
» August 2006
» January 2006
» August 2005
» July 2005
» June 2005
» May 2005
» April 2005
» March 2005
» February 2005
» January 2005
» December 2004
» November 2004
» October 2004
» September 2004
» August 2004
» July 2004
» June 2004
» May 2004
» April 2004
» March 2004
» February 2004
» January 2004
» December 2003
» November 2003
» October 2003
» September 2003
» August 2003
» July 2003
» June 2003
» May 2003
» April 2003
» March 2003
» February 2003
» January 2003
» December 2002
» November 2002
» October 2002
» September 2002
» June 2001

You are currently browsing the archives for the Radio category.

Archive for the 'Radio' Category

« Previous Entries


Viewing large Images with Scrollbars Using Python, Tk, and PIL
Posted by
Scott March 3rd, 2010 | 5,253 words | No Comments »

I don’t have much time to write (I have a million things to do over this spring break!) but I’ll try to post what I discover as I go with the hope that it might help someone else out there. It’s amazing how simple this program is, and it’s frustrating how long it took me to figure out how to code the darn image viewer thing. So, for what it’s worth, here it is.
specview

This little Python program will load an image (pretty much any format) using the Python Imaging Library (which must be installed) and allows you to see it on a scrollable canvas (in two directions) with Tkinter and ImageTk. The above screenshot is of the program viewing the image below:
1hr_original

What is that image? I won’t get ahead of myself, but it’s about 5kHz above 10.140mHz (30m radio band) including a popular QRSS calling frequency representing an entire hour of data. Also, I did all of the math myself with my own python scripts (yes, it eats WAV files and spits out spectrographs). Note that the JPG I uplaoded “full size” is no where near the ACTUAL full size of the image. It’s gagillions of pixels in all directions. Sweet. My goal is to have it scroll in the TK window, with slide-adjustable brightness/contrast/etc. I’ll get there eventually.

from Tkinter import *
import Image, ImageTk

class ScrolledCanvas(Frame):
     def __init__(self, parent=None):
          Frame.__init__(self, parent)
          self.master.title("Spectrogram Viewer")
          self.pack(expand=YES, fill=BOTH)
          canv = Canvas(self, relief=SUNKEN)
          canv.config(width=400, height=200)
          #canv.config(scrollregion=(0,0,1000, 1000))
          #canv.configure(scrollregion=canv.bbox('all'))
          canv.config(highlightthickness=0)

          sbarV = Scrollbar(self, orient=VERTICAL)
          sbarH = Scrollbar(self, orient=HORIZONTAL)

          sbarV.config(command=canv.yview)
          sbarH.config(command=canv.xview)

          canv.config(yscrollcommand=sbarV.set)
          canv.config(xscrollcommand=sbarH.set)

          sbarV.pack(side=RIGHT, fill=Y)
          sbarH.pack(side=BOTTOM, fill=X)

          canv.pack(side=LEFT, expand=YES, fill=BOTH)
          self.im=Image.open("./1hr_original.jpg")
          width,height=self.im.size
          canv.config(scrollregion=(0,0,width,height))
          self.im2=ImageTk.PhotoImage(self.im)
          self.imgtag=canv.create_image(0,0,anchor="nw",image=self.im2)

ScrolledCanvas().mainloop()



Simple DIY Stealth Apartment Antenna for 20m and 40m!
Posted by
Scott February 7th, 2010 | 5,253 words | 4 Comments »

I have no money for a HF antenna, and even if I did my apartment complex wouldn’t allow it! This is my story, and while I’m no expert I hope that sharing my experience will help encourage others to try crazy things in the spirit of invention. A friend loaned me a Century 21 HF CW-only transceiver which puts out ~20W. As far as an antenna, I was limited to what I could build. I tried a bunch of different designs, including a trash-brew 40m base-loaded vertical, but it didn’t work that well. I found that a “contorted dipole” (I heard it’s officially called a zig-zag design) strung up on my ceiling works surprisingly well. I’ve only had it up a few days, but from Florida I’ve talked to New York on 40m at 20W and Maine on 20m using 20W. Keep in mind that I’m brand new to CW, and that 99% of the conversations out there are way too fast for me to copy, so my greatest limitation is finding a CQ slow enough that I can respond to it.

The beauty of my antenna is four-fold. First, it’s super-cheap (a few bucks worth of parts). Second, it’s off the floor and out of the way (unlike my vertical). Third, it doesn’t require a tuner once it’s set up. Forth, it’s virtually invisible! Seriously, if you walk in my apartment you’d have no idea it’s there unless someone points it out. Check it out…

FROM MY FRONT DOOR:
dipole_apartment_1

THERE ARE 7 WIRES! CAN YOU SEE THEM? BARELY!
dipole_apartment_2

So, will this fly for you? That’s between you and your XYL. Here’s a diagram of my apartment and the antennas which hopefully should make a lot more sense…
apartment_dipole
The orange lines represent a 20m dipole with 2 ground radials rather than 1. The purple lines represent a 40m dipole. Dotted gray lines represent fishing line tied to the end of a wire to keep it stretched. Blue circles are plant hooks. Measurements are similar to regular dipoles (approx. quarter wavelength per leg), but I cut these long and used an antenna tuner to shorten them until I reached a 1:1 SWR. Once the SWR was set, I returned my borrowed antenna analyzer and the resulting antenna network seems pretty stable! I’m totally impressed with myself.
IMG_3091
not too shabby ‘eh?
IMG_3084

The physical assembly involved a package of ceiling-mount (screw-type) plant hooks and a couple packages of 50′ of picture hanging wire from Target (a few bucks total). The coax to the radio is pretty straightforward. Just a short patch of cable running up to the ceiling, then the shield goes one direction (to the 3 ground wires) and the center wire goes in the other direction (to the antenna elements). Both antennas are permanently soldered together, which is fine because SWR stays low and I don’t have to jumper things around when I want to change bands.

Here are some photos to show it more clearly…
IMG_3074
and, yes, that’s a sketch of a painting I haven’t painted yet
IMG_3075

DONT GET CONFUSED BY THOSE COILS! They’re not used for the antenna!!! They’re just there to help weigh down the wire to prevent it from wobbling due to the AC. Seriously, they do nothing, you don’t need them. They’re not even touching the antenna! Which reminds me, the two 20m radials were made from actual wire (because I had it lying around), so they’re coated in yellow. No biggie! No reason other than convenience that I didn’t use the picture hanging wire. Okay, that sums it up.

I hope this information helps! If you build a similar setup, let me know – I’d love to see it. If you have questions, feel free to email me. Remember, I didn’t put much math into this – I just went with approximately quarter wavelength legs and started cutting them until the SWR was down to 1:1, then I didn’t adjust it any more. It’s been several days and SWR seems stable, so no antenna analyzer is needed anymore. Good luck with your project, and with any luck I’ll work ya’ on the band. 73!
–Scott, AJ4VD



Converting ASCII Text to CW Morse Code with Linux
Posted by
Scott February 2nd, 2010 | 5,253 words | No Comments »

I wanted a way to have a bunch of Morse code mp3s on my mp3 player (with a WPM/speed that I decide and I found an easy way to do it with Linux. Rather than downloading existing mp3s of boring text, I wanted to be able to turn ANY text into Morse code, so I could copy something interesting (perhaps the news? hackaday? bash.org?). It’s a little devious, but my plan is to practice copying Morse code during class when lectures become monotonous. [The guy who teaches infectious diseases is the most boring person I ever met, I learn nothing from class, and on top of that he doesn't allow laptops to be out!] So, here’s what I did in case it helps anyone else out there…

Step 0: GET THE REQUIRED PROGRAMS! Yes, there’s a step zero. Make sure you have installed Python, cwtext, and lame. Now you’re ready to roll!

Step 1: PREPARE SOME TEXT! I went to Wikipedia and copy/pasted an ENTIRE article into a text file called in.txt. Don’t worry about special characters (such as ” and * and #), we’ll fix them with the following python script.

import time
f=open("out.txt")
raw=f.read()
f.close()

cmd  = """echo "TEST" | cwpcm -w 7 | """
cmd += """lame -r -m m -b 8 --resample 8 -q9 - - > text.mp3"""

import os
i=0
for chunk in raw.split("\n")[5:]:
        if chunk.count(" ")>50:
                i+=1
                print "\n\nfile",i, chunk.count(" "), "words\n"
		do = cmd.replace("TEST",chunk).replace("text","%02d"%i)
		print "running:",do,
		time.sleep(1)
		print "\n\nSTART ...",
                os.system(do)
		print "DONE"

Step 2: MAKE MP3s OF THE TEXT! There should be a new file, out.txt, which is cleaned-up nicely. Run the following script to turn every paragraph of text with more than 50 words into an mp3 file…

f=open("out.txt")
raw=f.read()
f.close()
cmd = """echo "TEST" | cwpcm -w 13 | sox -r 44k -u -b 8 -t raw - text.wav"""
cmd+="""; lame --preset phone text.wav text.mp3; rm text.wav"""
import os
i=0
for chunk in raw.split("\n")[5:]:
	if chunk.count(" ")>50:
		i+=1
		print i, chunk.count(" "), "words"
		os.system(cmd.replace("TEST",chunk).replace("text","%02d"%i))

Now you should have a directory filled with mp3 files which you can skip through (or shuffle!) using your handy dandy mp3 player. Note that “-w 13″ means 13 WPM (words per minute). Simply change that number to change the speed.

Good luck with your CW practice!
–Scott (AJ4VD)



Rainy Mornings and Boring Bicuspids
Posted by
Scott January 30th, 2010 | 5,253 words | No Comments »

Alas, another fleeting patch of free time has been bestowed upon me. Just like last semester, this semester (my second in a 4 year dental school) started off tough from day one. After Christmas break it was hard to walk into class at 8am and hit the ground running, but I managed to get the hang of it after a few days. I’ve heard medical school being described as “trying to drink from a fire hydrant”, but I think a more appropriate analogy would involve a treadmill set too fast. You have to work as hard as you can as soon as your feet touch the ground, and you might be able to keep up for now but you don’t know how much longer you can go before you tumble. I never really tumble, but I always feel like I’m about to. Overall, I can’t complain. I’ve managed to compartmentalize dental school into a chunk of my schedule (albeit a massive chunk), leaving time to spend with my family (wife) and when she’s at work, time to spend playing with electronics (which seems to be radio at this stage of my life).

Rather than bore the internet with descriptions of what I’ve been up to in dental school, I’ll focus on the interesting aspects of my most recent endeavors. A few weeks ago I took the final (third-level, extra class) amateur radio license exam. 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 passed [whew!] and applied for a new call sign (extra class operators can have shorter call signs). 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 [.- .--- ....- ...- -..]. Speaking of code, I made my first contact in Morse code from my apartment! Let me set the scene for you…
Ten_Tec_Century_21
This is the radio I’m using. It’s a Ten-Tec Century 21 HF CW transceiver which puts out ~30W.
antennaBig
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)…
antennaBigger
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…
antennaCoil
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! How cool is that?

And, as a closing note, Misia performing “Everything” (my favorite song) in Seoul, Korea:



CEJ, QRSS, and Life Recontemplation
Posted by
Scott January 1st, 2010 | 5,253 words | 2 Comments »

Yes, I did it. I’m probably the first (and let’s hope the last) human to ever write CEJ (the abbreviation for the cemento-enamel junction, a dentistry term) immediately before the letters QRSS (extremely slow speed Morse code transmissions, a radio frequency term). Anyhow, thanks in part to the temporary cessation of the tortuously monotonous dental school I’m enrolled in, I have had some time to put into random obscure hobbies. For example, I’ve become somewhat obsessed with QRSS, an obscure sub-niche of amateur radio (ham radio).

I only have a couple of minutes to write, so I’ll be concise. In brief, 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 guys sending messages with 100 milliwatts (one tenth of a watt! you can get more than that from a couple AA batteries!). The theory 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.
qrss_kj4ldf

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.140mhz from a Mosley-pro 67 yagi mounted ~180 ft in the air connected to a Kenwood TS-940S transceiver sending data to a PC through a SignaLink USB sound card. 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. Notice my call sign (KJ4LDF) at the bottom of the page!
qrss_transmitter

^^^ That’s the ACTUAL transmitter I’m hearing from Canada!!!

I don’t know why I’m drawn to QRSS. 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 SDRs (software-defined radios) they don’t require complicated equipment, and a QRSS transmitter or receiver can be built from a few bucks’ worth of parts.

HELPFUL FOR ANYONE WHO USES ARGO!!! 10_01_01_00009There’s a popular QRSS “grabber” software for Windows called Argo. It dumps out screenshots of itself every few minutes, but doesn’t assemble them together!!!!! It’s so annoying. I therefore took it upon myself to write a script to assemble several (or thousands) of Argo screen dumps together as a single image. It’s a script for ImageJ. To use it, first install MBF’s ImageJ. Open ImageJ, drag and drop a DIRECTORY of screenshots / captures into the program to open them as a stack, make a new macro, copy/paste the following code into it, and hit CTRL+R to run it, and poof! The output is a gorgeous panoramic shot like below.long

And here’s the script to automate the process…

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);
}
//run("8-bit");
//run("Enhance Contrast", "saturated=0.5");
selectWindow("source");
close();

As far as life recontemplation 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 (goals?) which are notoriously difficult, and further challenge myself by doing things in weird, quirky ways. For example, I’d love to get into radio, BUT I HAVE NO MONEY!. Yeah, an all-band 100-watt HF/VHF/UHF rig would be nice, but I don’t have hundreds of dollars to fork over. Worse yet, in the technical sense, I do, I’m just trying to be responsible and saving it for emergencies / tuition and waiting until I’m a dentist (a.k.a. have a job) before I spend money on things that make me happy. Anyway, without complaining I built a non-elegant 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! Here it is on my balcony…
ant_1

As you can see, it’s ghetto. Yes, that’s an antenna made from copper pipe, wire, and toilet paper rolls. I’ve 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 adequate grounding for 40m operation. The antenna feeds into a Pixie II direct conversion receiver / QRP transmitter which dumps its output to a laptop computer. Note that I did *NOT* use this setup to receive my beautiful QRSS signals. With that being said, I have copied PSK-31 transmissions from Canada with this setup. It works way better than a long / random wire antenna because it dramatically reduces noise. Here’s a closeup of the tap points on the inductor base…
ant_2

That’s it for today folks! Back to my crazy projects. Take care!

UPDATE: VA3STL mentioned me on his site! Woo hoo!



HF CW on the Cheap
Posted by
Scott December 10th, 2009 | 5,253 words | 4 Comments »

Okay, here’s a preview of my project plans for Christmas break! My goal is to make a fun and comfortably functional HF CW transmitter / direct-conversion receiver capable of working any HF band by merging the incredibly simple and cheap (~$10) Pixie II CW transceiver with a SI 570 digital programmable oscillator controlled by an ATTiny 2313 microcontroller! (it’s often used in SDRs controlled by USB as seen here).

AUDIO FILES!!! [80m.mp3] and [40m.mp3] are already ready already!

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!
cw_everything
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.
cw_coke
cw_close
cw_open2
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.
40mPSK
Decoding of PSK31 is happening here, using output from my circuit with the 40m crystal in it.

AUDIO FILES approx. 1 minute in length from my circuit:
>> [80m.mp3]
>> [40m.mp3]

Wish me luck! This project is just beginning…

now for a random video/song:



PySquelch: A Python-Based Frequency Audio Activity Monitor
Posted by
Scott July 26th, 2009 | 5,253 words | 1 Comment »

I’m pretty much done with this project so it’s time to formally document it. This project is a collaboration between Fred, KJ4LFJ who supplied the hardware and myself, Scott, KJ4LDF who supplied the software. Briefly, a scanner is set to a single frequency (147.120 MHz, the output of an active repeater in Orlando, FL) and the audio output is fed into the microphone hole of a PC sound card. The scripts below (run in the order they appear) detect audio activity, log the data, and display such data graphically. Here is some sample output:
test_24hr-1
test_average
test_alltime-1
test_60min

Live-running software is current available at: Fred’s Site. The most current code can be found in its working directory. For archival purposes, I’ll provide the code for pySquelch in ZIP format. Now, onto other things…



Field Day 2009
Posted by
Scott June 29th, 2009 | 5,253 words | 2 Comments »

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).
scottpaper

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. scottpaperzoomSeeing (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]



pySquelch – Frequency Activity Reports via Python
Posted by
Scott June 18th, 2009 | 5,253 words | No Comments »

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. First, I’ll entice you by showing some nice graphs of the output! I ran the code below for 24 hours and this is the result…
1png
Pretty good ‘eh? This graph represents traces of the 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.
2png

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, random, alsaaudio, 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. Awesome! The data presented here can be graphed (producing the image at the top of the page) using the following code:

#pySquelchGrapher.py
print "loading libraries...",
import pylab, datetime, numpy
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()

If you have any questions, Google first, then feel free to contact me if you still can’t get it. Good luck!!



Updated pySquelch Code
Posted by
Scott June 18th, 2009 | 5,253 words | No Comments »

While working to improve the python-based frequency activity logger I wrote a couple of entries back, I greatly increased its accuracy. I won’t go into the details other than saying that it simply polls the sound card a few times a second to listen for when a transmission begins and ends. The new data file format appears like this…

1245316480,5
1245316803,3
1245316839,6
1245316848,11
1245316867,5
1245316875,12
1245316893,13

The major change is that the date and time is recorded as seconds since epoch (Unix time) and the duration of the transmission is given after the comma. This greatly simplifies post-processing. Here is the revised python code to poll the sound card and generate such a log file:

##################################
##### UPDATED CODE IS IN A NEWER ENTRY #####
### CHECK THE PYTHON AND RADIO CATEGORIES ###
##################################
import time, random, alsaaudio, 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\n"%(start,length)
	addToLog+=newLine
	if start-lastLogTime>30: updateLog() # update the log

while True:
	findSquelch()

To test the functionality of the code visually, you can use this quick and dirty method of graphing the log files output by this program. Keep in mind THIS IS NOT INTENDED TO BE USED other than to simply test the program.

##################################
##### UPDATED CODE IS IN A NEWER ENTRY #####
### CHECK THE PYTHON AND RADIO CATEGORIES ###
##################################
import pylab
import time, datetime
f=open('log.txt')
raw=f.read()
f.close()
raw=raw.split("\n")
pylab.figure()
for line in raw:
        if len(line)<5:continue
        line=line.split(",")
        sec=datetime.datetime.fromtimestamp(int(line[0]))
        dur=int(line[1])
        sec2=datetime.datetime.fromtimestamp(int(line[0])+int(line[1]))
        pylab.fill([sec,sec,sec2,sec2],[0,1,1,0],'k',lw=0,alpha=.5)
pylab.show()
« Previous Entries
copyright © 2006 swharden@gmail.com