Upcycle finished! The Hybrid Radio is ready
I published the first draft pic as a comment to my last post #10. Here is a better one.
This is my last post for the Upcycle-it Challenge,
During the last week of the project everything had to go according to plan. On Friday, June 2nd noon time, I had an almost finished radio that still needed some finishing touches, but it was working.
While I was in the process of preparing it and the software ready, more ideas and features came to my mind. Things that if you do not have something up and running to familiarize yourself with, you cannot even think of.
We live in the IoT era and experiment with it. I am not sure what the next leap will be. One thing is sure, I have come a long way from my first Intel 8080 and the ARPANET of the 300/75 bps modems to our wifi super fast society. I decided to participate in this Challenge as I wanted to experiment with new ideas. Intel Edison proved to be quite an interesting and powerful platform. As I have another board I am thinking to use for an upgraded version of the Web SDR Analog Radio Client.
It would be really nice if a friend or fellow Ham could share directly with you an interesting or rare reception the moment it takes place, and you could listen to it on your hardware right away.
Web SDR movement expands. Have a look at www.sdr.hu, and you can get a taste of it. Companies like ICOM, specializing in Amateur Radio equipment start using SDR technology in their products. KiwiSDR (http://kiwisdr.com/KiwiSDR/) lets you build a Wideband Web SDR server with a BeagleBone Black.
Although I have accomplished my task for my 1978 Panasonic vintage receiver, I want to continue my experimentation. There are plenty of vintage receivers to Upcycle, adding various flavors to them.
The idea with the OLED display and the building block from Sparkfun gave me the smallest possible footprint, but for the next project, I would opt for a larger screen.
Tuning to the same station both from my location through the analog portion of my receiver and the Web SDR portion of my Intel Edison I can compare the reception conditions of the same broadcasting transmission at two different locations in one panel and come with useful observations about Wave Propagation, just to name one important feature.
Wrapping the Panasonic receiver
Well, before wrapping it up, I had to open my receiver, carefully remove the antenna wire and the battery connections and put away the back comer. Then I removed the power supply, the speaker, the logo name plate and eventually the speaker metal front grill.
I decided to use a plexiglass front plate from a scrapped device that was a short of magnifying glass as well.
I mounted the Edison block on it with four screws. The most difficult part was to find a knob for the joystick button. All the possible places that I visited had knobs that had a larger square diameter than the one that was needed which was 2.2 mm. That was a problem. I should have thought of it earlier, but I was always under the impression that shops that sold such joystick buttons, would also have the right knob for it. As that was not true, I thought that I could print a 3d button from the local 3dhub print shops, but I needed the file in the right format for it. Delivery was next day, so I was still withing my plan. But I did not find such file. I contacted Sparkfun’s support, but they only had the pdf file of the switch that I had in the meantime traced (SF303GJ26-3).
So I decided to make one myself with the help of a friend that is more experienced on such mechanical tasks. The plastic axle of an Alps potentiometer was used for the purpose, as it had a small hole in the center. It was cut in the right length and the hole was enlarged to 2,2 mm so that it could be fit on the joystick. It was successfully fitted by 03:15 on Thursday morning and to mark the occasion we named our effort as the Midnight project.
We also had to change the two buttons on the OLED board, with ones that had longer axle, but that less difficult. We then cut a conical hole on the plexiglass for the joystick button and by 06:30 on Friday morning the Midnight project was over. During that time we also had to cut and bend the grill, fit a new 2 gang push button change-over switch and put everything together using simple tools. It was a great experience for me.
After some hours sleep, I had to make a small LM317 regulator board that would power the Edison with 4 volts, to be on the safe side, make the Audio and power connections and put everything back hoping that it would work.
That “easy” task took time to complete, as the schematics that I had found did not correspond to the pcb’s of my radio, but with patience everything was gradually resolved and I ended up with a working radio, a dual mode Hybrid, Web SDR & Analog Receiver. One of its kind if you really think of it. It combined all kind of technologies. A mechanical frequency dial, a switchable frequency counter and a Web SDR client with an OLED display powered by an Intel Edison. The 1978 receiver had been Upcycled to modern standards.
I was the proud and happy owner of it. Panasonic DR-28/ MK-I.
The last part of the software
In my last post, I had stopped with a version of the software that did not include and display on the screen. So I had to add the methods of displaying the Client commands on the screen.
While doing that, I had to adapt a basic concept of the client which was the number of frequency steps to match with the Web SDR server. Three frequency steps are provided. big (+++), medium (++) and small (+), which work cyclically with the Left and Right buttons of the Joystick. They correspond to +:0.1kHZ, ++:1kHz, +++:2.5/9kHz depending on the Mode.
Big frequency change is accomplished from the two separate Band switches (A and B) of the OLED module. My assigned bands are: LW, MW, 160m, 80m, 40m, 30m, 20m, 18m, 15m, 12m, 10m.
Mode (demodulation) is controlled from the Joystick’s center button and is assigned as: CW, LSB, USB, AM, FM, AM-s (Am-Sync).
Assuming that you select LW band where the broadcasts using AM modulation, the Frequency Stepping becomes 9 KHz(+++), 1 KHz(++), 0,1 KHz(+) . As Long Wave has a range of frequencies that expands from 153 KHz to 279 with a spacing of 9 KHz between the stations, even a single step of (+++) would be enough.
The code is listed below:
'use strict'; const CDP = require('chrome-remote-interface'); const mraa = require('mraa'); var edison = require('../node_modules/edison-oled/build/Release/edisonnodeaddon'); var keypress = require('keypress'); var verbose = true; var delay = 50; var smallDelay = 10; var sleepTime = 5000; /* screen is 64 X 48 */ var lcdWidth = edison.LCDWIDTH; var lcdHeight = edison.LCDHEIGHT; var oled = new edison.Oled(); function cleanUp() { oled.clear(0); oled.display(); } function initOled() { if(verbose) { console.log('--setup screen'); } oled.begin(); oled.clear(0); oled.display(); oled.setFontType(0); lcdWidth = oled.getLCDWidth(); lcdHeight = oled.getLCDHeight(); if(verbose) { console.log('---width: ' + lcdWidth); console.log('---height: ' function printError(message) { console.log("Err: " + meesage); printErrorOled(message); } function printErrorOled(message) { oled.clear(0); oled.setFontType(1); oled.setCursor(0, 0); oled.print("Error!"); oled.setFontType(0); oled.setCursor(0, 15) oled.print(message); oled.display(); } CDP((client) => { function killClient() { client.close(); cleanUp(); } var args = process.argv; if(args.length != 4) { console.log("Invalid execution: node websdr_controller_edison.js freq mode"); killClient(); } else { var MAX_STEP = 3; var step = 3; var currentMode = args[3]; var currentFrequency = parseFloat(args[2]); const bands = [153, 522, 1800, 3500, 7000, 10100, 14000, 18068, 21000, 24890, 28000]; const bandNames = ['LW', 'MW', '160m', '80m', '40m', '30m', '20m', '18m', '15m', '12m', '10m']; const modes = ['cw', 'lsb', 'usb', 'am', 'fm', 'amsync']; const modeNames = ["CW", "LSB", "USB", "AM", "FM", "AM's"]; + lcdHeight); } } initOled(); function printError(message) { console.log("Err: " + meesage); printErrorOled(message); } function printErrorOled(message) { oled.clear(0); oled.setFontType(1); oled.setCursor(0, 0); oled.print("Error!"); oled.setFontType(0); oled.setCursor(0, 15) oled.print(message); oled.display(); } CDP((client) => { function killClient() { client.close(); cleanUp(); } var args = process.argv; if(args.length != 4) { console.log("Invalid execution: node websdr_controller_edison.js freq mode"); killClient(); } else { var MAX_STEP = 3; var step = 3; var currentMode = args[3]; var currentFrequency = parseFloat(args[2]); const bands = [153, 522, 1800, 3500, 7000, 10100, 14000, 18068, 21000, 24890, 28000]; const bandNames = ['LW', 'MW', '160m', '80m', '40m', '30m', '20m', '18m', '15m', '12m', '10m']; const modes = ['cw', 'lsb', 'usb', 'am', 'fm', 'amsync']; const modeNames = ["CW", "LSB", "USB", "AM", "FM", "AM's"]; var currentBand = 0; var currentModeId = 3; var btnUp, btnDown, btnLeft, btnRight, btnSelect, btnA, btnB; function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; function sleep(ms) { var now = new Date().getTime(); while(new Date().getTime() < now + ms){ /* do nothing */ } } function startScreen() { if(verbose) { console.log('--show start'); } oled.clear(); oled.setCursor(9, 0); oled.print("EDISON's"); oled.setCursor(0, 11); oled.print("Web SDR ON"); oled.setCursor(0, 23); oled.print("design by:"); oled.setCursor(3, 35); oled.setFontType(1); oled.print("SV1ONW"); oled.display(); } function mainScreen(frequency, mode, step, band) { oled.clear(0); oled.setFontType(1); let firstPartFreq = Math.floor(frequency); oled.setCursor(calcOffset(firstPartFreq)*8, 0); oled.print(firstPartFreq); // First part of Frequency string oled.setCursor(46, 4); oled.setFontType(0); let secondPartFreq = (frequency % 1).toFixed(3).substring(2) oled.print(secondPartFreq); // Second part of Frequency string oled.setCursor(0, 15) oled.print("Mode: " + mode); //AMsync becomes AM's oled.setCursor(0, 26); oled.print("Band: " + band); oled.setCursor(0, 37); oled.print("Step: " + step); oled.display(); } function calcOffset(num) { if(num > 0 && num < 10) { return 4; } else if(num >= 10 && num < 100) { return 3; } else if(num >= 100 && num < 1000) { return 2; } else if(num >= 1000 && num < 10000) { return 1; } else if(num >= 10000) { return 0; } } function initButtons() { btnUp = initButton(46, freqUp); btnDown = initButton(31, freqDown); btnLeft = initButton(15, stepLeft); btnRight = initButton(45, stepRight); btnSelect = initButton(33, nextMode); btnA = initButton(47, bandUp); btnB = initButton(32, bandDown); } function deregisterButtons() { btnUp.isrExit(); btnDown.isrExit(); btnLeft.isrExit(); btnRight.isrExit(); btnSelect.isrExit(); btnA.isrExit(); btnB.isrExit(); } function initButton(pinIn, callback) { let btn = new mraa.Gpio(pinIn); btn.dir(mraa.DIR_IN); btn.isr(mraa.EDGE_RISING, callback); return btn; } function intro() { console.log("Frequency down ['j'] / up ['k'].\nStep size down: ['h'] / up ['l'].\nBand down: ['i'], Band up: ['o'].\nC$ printStatus(); } function addKeyPressListener() { // make `process.stdin` begin emitting "keypress" events keypress(process.stdin); // listen for the "keypress" event process.stdin.on('keypress', keyPressEvent); process.stdin.setRawMode(true); process.stdin.resume(); } function changeStep(stepIn) { step = stepIn; printStatus(); } function changeFreq(up, step) { var str = up ? '+' : '-'; client.send('Runtime.evaluate', {'expression': 'freqstep(' + str + step + '); nominalfreq();'}, (error, response) => { if(error) { console.log(error); return; } currentFrequency = response.result.value; printStatus(); }); //if(verbose) // console.log('stepped ' + (up ? 'up' : 'down')); } function setFreq(freq) { client.send('Runtime.evaluate', {'expression': 'setfreq(' + freq + '); nominalfreq();'}, (error, response) => { if(error) { console.log(error); return; } currentFrequency = response.result.value; printStatus(); }); } function setBand(up) { if(up) { currentBand += 1; currentBand = currentBand >= bands.length ? 0 : currentBand; } else { currentBand -= 1; currentBand = currentBand < 0 ? bands.length - 1 : currentBand; } setFreq(bands[currentBand]); } function setMode(mode) { client.send('Runtime.evaluate', {'expression': 'set_mode(\'' + mode + '\'); nominalfreq();'}, (error, response$ if(error) { console.log(error); return; } currentFrequency = response.result.value; printStatus(); }); currentMode = mode; } function queryFrequency() { client.send('Runtime.evaluate', {'expression': 'nominalfreq()'}, (error, response) => { if(error) { console.log(error); return; } currentFrequency = response.result.value; }); } function printStatus() { let currentModeName = modeNames[currentModeId]; let currentBandName = bandNames[currentBand]; let currentStepStr = step == 1 ? "+" : (step == 2 ? "++" : "+++"); console.log('Frequency: ' + parseFloat(currentFrequency).toFixed(3) + 'kHz, Band: ' + currentBandName + ', Mode: ' + c$ mainScreen(currentFrequency, currentModeName, currentStepStr, currentBandName); } function keyPressEvent(ch, key) { //console.log('got "keypress"', key); if (key && key.name == 'k') { freqUp(); } else if (key && key.name == 'j') { freqDown(); } else if(key && key.name == 'h') { stepLeft(); } else if(key && key.name == 'l') { stepRight(); } else if(key && key.name == 'o') { bandUp(); } else if(key && key.name == 'i') { bandDown(); } else if(key && key.name == 'm') { nextMode(); //queryFrequency(); } else if (key && key.ctrl && key.name == 'c') { process.stdin.pause(); killClient(); } } var freqUp = debounce(function() { changeFreq(true, step); }, smallDelay); var freqDown = debounce(function() { changeFreq(false, step); }, smallDelay); var stepLeft = debounce(function() { step = step - 1; step = step < 1 ? MAX_STEP : step; changeStep(step); }, delay); var stepRight = debounce(function() { step = step + 1; step = step > MAX_STEP ? 1 : step; changeStep(step); }, delay); var nextMode = debounce(function() { currentModeId += 1; currentModeId = currentModeId >= modes.length ? 0 : currentModeId; currentMode = modes[currentModeId]; setMode([currentMode]); }, delay); var bandUp = debounce(function() { setBand(true); }, delay); var bandDown = debounce(function() { setBand(false); }, delay); intro(); //addKeyPressListener(); startScreen(); sleep(sleepTime); if(verbose) { console.log('--show main screen'); } setFreq(currentFrequency); printStatus(); initButtons(); } }).on('error', (err) => { // cannot connect to the remote endpoint printError(err); });
Autostart
If you want to make the client start automatically when you boot Edison, follow these steps (adapted from Stepanie Moyerman's blog post):
- Disable the keyboard event listener by commenting out the following line in
websdr_controller_edison.js
:
. . . intro(); // addKeyPressListener(); <--- (line 330) startScreen(); sleep(sleepTime); if(verbose) { console.log('--show main screen'); } setFreq(currentFrequency); printStatus(); initButtons(); . . .
- Create a shell script in
/etc/init.d
, by calling it something likestart_websdr.sh
and add the following (make sure you enter the correct path to where you have cloned the client into, and the parameters for the starting script; see above for details):
#!/bin/sh ### BEGIN INIT INFO # Provides: start_websdr # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts the websdr client ### END INIT INFO cd path_to_where_websdr_client_is_installed ./websdr.sh websdr_server_url start_freq start_mode
- Make the script you have just made executable via
chmod +x start_websdr.sh
- Finally, make sure the script is executable every time linux boots:
$ cd /etc/init.d/ $ update-rc.d start_websdr.sh defaults
For the latest version of the code, the initialization script and the automation script files, including installation instructions, you can look at the following GitHub link: https://github.com/sinantie/websdr
The video
Thanks to my colleague and good friend Thanos, SV1IVK, I have a better video now.
Credits
In closing I would like to thank my wife Roula for her patience during the whole time of the project, my son and fellow Radio Ham Yannis for his great assistance with code development and js, my daughter-in-law Maria for sharing her web experience with me and our family friend George that helped me with the mechanical portion of the portion.
Thank you team.
Top Comments