Quick and Easy code blocks in Google Docs

Posted on: 2015-03-09 09:53:12

Want some quick and easy code blocks in Google Docs? Just do this:

  1. Make a table.
  2. Remove the border (make it 0px), and add your background color.
  3. Add a numbered list.
  4. Right click on the list and choose "Edit prefix and suffix..."
Continue reading...

Web Weariness

Posted on: 2014-09-10 22:41:13

A while back I was doing a bit of research on web standards. Specifically, I was looking at the number of documents that were published by the W3C over time. The last several years have seen an explosion of new recommendations with everything from CSS to Web Telephony. Let's face it, the web is here to stay. Technology points to the web. The Internet of Things surrounds us.

The number of technologies that power those things seem to grow every day. Because the internet changes so rapidly, new variants of existing languages pop up, new frameworks are created and destroyed. Optimization occurs. Repos get branched. Integrations get built. New SaaS platforms get launched. Every ounce of ROI gets squeezed out.

10 years ago, a guy could learn PHP and SQL (with a little CSS & JavaScript) and build a pretty decent app. And while it's still possible for one-man-shows to build and launch a site, it's getting harder to do. Sure, the number of modules that are available for platforms like Drupal, Magento, Laravel, Symfony, ZF2, PHP, Ruby, Rails, Python, Django, Perl, Node.js, Java, etc., etc. are growing at staggering rates. Which technology you choose is a matter of personal preference, but Polyglots are fairly normal to come across in most shops now. This is practically a requirement and not just a sign of someone who loves this stuff.

Why is this the case?

Three observations.

  1. Websites being built are getting more complex and intricate because a bigger internet means you must differentiate yourself.
  2. The code bases and toolsets required to build those complex and intricate things are getting larger and more diverse.
  3. The knowledge required to use those code bases is also expanding, but perhaps not as rapidly since frameworks help to make interop better.

The one-man-custom shop is slowly dying. You can still do well focusing on a particular piece of the puzzle, but the expectation that one person can do it all is quickly becoming less of a reality. It's a full-time job just to keep up with the changes, let alone spending the time to actually implement them.

So the conundrum that I see right now is that while things are getting easier to build, they are requiring more complexity. And that complexity seems to be winning in terms of where the "how much do I have to know to build this" gauge is moving.

Sometimes I yearn for something simpler: Needing only a language or two. Solid, one-way approaches to implementation. Solving real problems instead of trying to figure out how to not pull your hair while accomplishing something that should be simple.

Continue reading...

PyAudioMixer: A more versatile Python Audio Mixer

Posted on: 2014-09-07 22:43:24

When I came across swmixer, it seemed it answered 90% of my needs for a mixer for an amateur radio application I was working on. However, the one thing that it didn't support that was a deal breaker was that only one mixer could be set up. This was no bueno.

The wonderful thing about swmixer, though, was that it seemed like it would lend itself well to being more objectified so that more than one mixer could be set up. And that's exactly what happened.

PyAudioMixer is a fork of swmixer with the following changes (roadmap items in italics):

  • Multiple discrete mixers
  • Microphone as a channel (with volume control)
  • Frequency and DTMF Generators
  • Support for unlimited length (live) audio streams (partially completed)
  • Mixer to mixer I/O
  • Output to file
  • Network transport & codec support

Patches and comments are welcome!

Continue reading...

Rewrapping wx.StaticText

Posted on: 2014-09-02 08:09:08

Per this thread (see the _Rewrap function in Robin's example code), you need to set the Label Text again in order for it to update properly:

def _Rewrap(self): 

You may also need to call self.Layout() (which is the parent of the StaticText element) in order to make sure everything is a-okay.

Continue reading...

Setting a wxPython Global Hotkey with a Regular Character

Posted on: 2014-08-31 22:33:21

If you're trying to set up a global hotkey in Windows, you can use the win32con.VK_* codes to set a hotkey that uses a regular character (e.g. Control-Alt-B), but on OSX it's not as clear. However, a quick look at the patch reveals that all you need to do is pass the ASCII character code for the character you want by using ord() like so:

result = self.RegisterHotKey(hkid,

And now, when you hit Control-Shift-R, the event will fire.

For what it's worth, you can also use ord() with wx.GetKeyState() to detect if a character has been pressed on the keyboard. Not sure how this works for international apps, but it works great for my purposes:

print wx.GetKeyState(ord('h')) # Prints true if the 'h' key is currently pressed
Continue reading...

2m/70m Antenna Testing Chart

Posted on: 2014-08-08 09:40:34

I've been in process of building and testing a dual-band 2m/70cm antenna for several weeks now. Recently, my antenna fell and it came time to rework it. A friend of mine was kind enough to loan me an <a href="">MFJ-269. During the tweaking process, it's convenient to be able to just keep track of SWR values over the bands as well as a place for notes and changes to be marked for each test. Maybe you'll find it useful.

Download below.

Continue reading...

Getting into Amateur Radio

Posted on: 2014-08-04 21:06:08

One of the things that we take for granted given our connected society is radio. It is strange how something invisible has had such a visible impact on our world and our culture. For the past 100+ years, radios have been conceived, built, improved, and now exist in everything from satellites to home security system window probes. They carry entertainment as well as troops orders. At any given moment, hundreds of communications from all around us are likely passing, unbeknownst, right through us.

At the coaxing of a friend of mine, I finally broke down and took my Technician test last December and passed, earning the call sign KF5ZQE. A little over a month ago, I took and passed the General Exam. Now, I'm studying for the Extra license, and hoping to pass it in the next 2 or 3 weeks.

Studying for the Extra license test has been a challenge. It covers a deep, comprehensive set of knowledge with some 900 questions in the pool. Topics range from the mundane FCC codes to the intricate explanation of a schematic. Although one could simply choose to "go through the motions" and pass the test through rote memorization. And although much of it seems to be covering lots of different, unrelated areas: e.g. calculating transmission line impedance, understanding the radiation patterns of antennas, and calculating the oscillation frequency of a Colpitts oscillator, many of the questions are inextricably linked in one large (for lack of a better word) cycle.

It's all just electricity and magnetism, pulsing and oscillating through space and time a roughly the speed of light. Electrons getting stored, resisted, inducted, resonated, and switched.

And it is magnificent and beautiful.

For his invisible attributes, namely, his eternal power and divine nature, have been clearly perceived, ever since the creation of the world, in the things that have been made. So they are without excuse. (Romans 1:20 ESV)

Continue reading...


Posted on: 2013-11-10 09:07:10



Continue reading...

A Quick Note Regarding ExFat Volumes Used On Windows & Mac Computers

Posted on: 2013-10-20 15:48:37

I've reverted to using 16GB SD Cards to move sundry items between an ASUS Laptop and a MacBook Pro. While not the speediest method, it is the fastest considering that:

  • The MacBook Pro lacks an onboard Gigabit Ethernet adapter (and I guess I'm too cheap to spend the $30 to get one).
  • Although I've got 802.11n running in full force the ASUS is stuck at G.
  • All my external big HDDs are HFS+ Formatted...
  • etc.

When formatting the SD card most recently, Windows decided to ask me what allocation unit size I wanted. Fair enough, I thought. I'll set it to something reasonable considering that I'm going to end up putting some rather large files on it.

Windows dialog box showing the available allocation unit sizes for ExFat

What's an allocation unit? Allocation unit sizes are basically the minimum amount of space that is needed to take up one file on a drive. For example: a 4K (kilobyte) allocation size means that even if you saved a file with the word "um" in it, it would still take up 4K on the drive itself. Why does this matter? Well, this matters because the computer needs to be able to keep track of what files go where and so each unit is recorded during formatting. I'll not go into the math here, but the implications are simple: If you know you're only going to be storing huge files (videos, MP3s, Virtual machines, etc.) on a device with limited resources such as an SD Card, you can "save" some space, by increasing the allocation unit, requiring less space to keep track of all of those units. Yeah, the difference is only a few megs, but it feels good to know I'm squeezing everything I can out of it!

Ahem. Anyway. So I formatted with a generous 16M allocation unit size. After all, I'm really only storing one or two files on this bad boy. All was great until I stick it into my Mac and noticed it wasn't mounting. A little more sniffing around in the logs and I found this:

10/19/13 9:37:41.754 PM diskarbitrationd[16]: unable to mount /dev/disk3s1 (status code 0x00000047).

I poured over the intertubes to no avail. I ultimately ended up having OSX format the drive. What appears to have happened was that OSX did not like the large block size I assigned to it. It seems to have formatted the device with a 128KB block size. (touch test; stat -f \"%k\" blah\" returned 131072).

So if you happen to have a problem like this, make sure your block size is 128KB (or maybe lower?) It appears that windows defaults to 32KB.

Note: Also, as mentioned before, if you do find a way to use it with a huge block-size, make sure you disable Spotlight on the drive so that you don't end up wasting space with the lots of little dotfiles that Spotlight puts on a partition.

Continue reading...

Custom Google Apps Scripts Spreadsheet Functions for Technical Sales

Posted on: 2013-10-19 11:09:09

A couple of years ago, I wrote some custom functions to help speed up working on estimates for our clients. Well, those functions have been lost since I left Classy Llama, but I think the these versions are much improved anyway.

Anyway, here are some Google Apps Script functions for Spreadsheets which might be useful for those in Technical Sales or software development. They can be used to add up and manipulate hour ranges (e.g. SUMRANGE(["1-2",2,"2-5"]) would yield "5-9"). Those functions are SUMRANGE, SUMRANGEHIGH, SUMRANGELOW, RANGEMULT, and RANGEADD.

There are also some functions which can sum up natural language times. Those functions are SUMTIME and TIMEHOURS. TIMEHOURS takes a string with a time length in natural language e.g. 1 week, 4 weeks, 2.5 weeks, 5 days, 1 w 4 d and turns them into an integer (hour) value. Those values are taken from the global variable at the top of the script. So based on the paste below, you'd get 40, 160, 100, 40, and 72 respectively.

SUMTIME takes a range and adds up the TIMEHOURS values for every value in the range and spits out the final value.

Finally, EFFORT(hours, frac=0) is a helper function to take the results of SUMTIME and provide a more elegant way of display the information. EFFORT(10) yields "1 d 2 h", EFFORT(52) yields "1 w 1 d 4 h". EFFORT(10, 1) yields "1.25 days" and EFFORT(160, 1) yields "4 weeks"). It tries to be smart about the way that it displays the information. If you pass in 1 (true) as the second param of EFFORT, you'll get a fractional value for the first (largest) result and the whole word for the unit.

Hope this helps someone. Posted as a gist so you can iterate. Have fun!

It is available as a Gist on Github.

/* Global vars. */ var hoursPerDay = 8; var hoursPerWeek = hoursPerDay * 5;


  • We calculate the number of hours in a given range. */ function SUMTIME(allData) { var numHours = 0; var numCells = allData.length;

for (var i = 0; i <= numCells; i++) { var value = allData[i]; //.getValue();

numHours += TIMEHOURS(value);

} return numHours; }


  • For a given value, return the amount of time in hours. / function TIMEHOURS(value) { / Short circuit for plain number values, which should be hours. */ if (typeof value == "number") { return value; }

var numHours = 0; var weekRegex = new RegExp(/(\d+(?:\.\d+)?)\W?w(?:ee)?k?s?/); var dayRegex = new RegExp(/(\d+(?:\.\d+)?)\W?d(?:ay)?s?/); var emptyRegex = new RegExp(/\d/);

if (weekRegex.test(value)) { numHours += (parseFloat(value) * hoursPerWeek); value = value.toString().replace(weekRegex.exec(value)[0], ''); } if (dayRegex.test(value)) { numHours += (parseInt(value) * hoursPerDay); value = value.toString().replace(dayRegex.exec(value)[0], ''); } if (!emptyRegex.test(value)) { // Don't touch it. } else { numHours += parseInt(value); }

return numHours; }


  • Return the value of a named range. */ function getNRValue(name) { return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(name).getValue(); }


  • For a given range, return an array with all of the data from the cells. This
  • mimics how functions used in formulas actually get their data. */ function getAllCells(someRange) { var range = someRange; var numRows = range.getNumRows(); var numCols = range.getNumColumns(); var cells = []; for (var i = 1; i <= numRows; i++) { for (var j = 1; j <= numCols; j++) { var currentValue = range.getCell(i,j).getValue(); cells.push(range.getCell(i,j).getValue()); } } return cells; }

/** Sum a range of ranges. Running SUMRANGE on this set of data: *

  • +-------+--------+
  • | 1 - 2 | 3 - 4 |
  • | 5 | 4 - 10 |
  • +-------+--------+
  • Would return "13 - 21".
  • Non range values are applied to both the high and low-end of the
  • data. To get either side of the range, use SUMRANGEHIGH() or
  • If you want to multiply/divide a range by something, use RANGEMULT().
  • If you want to add/subtract to a range, use RANGEADD(). */ function SUMRANGE(data) { var retVal = actuallySumRange(data); return retVal[0] + " - " + retVal[1]; }


  • This is where all the logic lives for actually summing a range. Hence
  • the name. */ function actuallySumRange(data) { var numCells = data.length; var low = high = 0.0; var replace = new RegExp(/\s/g); var value = '';

for (var i = 0; i <= numCells; i++) { if (typeof data[i] != "object") continue; value = data[i].toString(); value = value.replace(/^\s\s*/, ""); thisCell = value.replace(/\s\s*$/, ""); if (thisCell.length == 0) continue;

var values = thisCell.split('-');
if (values.length == 2) {
  low += parseFloat(values[0].trim());
  high += parseFloat(values[1].trim());
else {
  low += parseFloat(values[0].trim());
  high += parseFloat(values[0].trim());      

} return [low, high]; }


  • Returns just the high end of a range. */ function SUMRANGEHIGH(someRange) { var retVal = actuallySumRange(data); return retVal[1]; }


  • Returns just the low end of a range. */ function SUMRANGELOW(data) { var retVal = actuallySumRange(data); return retVal[0]; }


  • Adds (or subtracts, if you use a number <0) a number from a range.
  • For example, RANGEADD("1 - 2", 4) would yield you "5 - 6". */ function RANGEADD(data, value) { var retVal = actuallySumRange(data); return (retVal[0]+value) + " - " + (retVal[1]+value); }


  • Multiplies (or divides, if 0 < value < 1) a number against a range.
  • For example, RANGEMULT("1 - 2", 4) would yield you "4 - 8". */ function RANGEMULT(data, value) { var retVal = actuallySumRange(data); return (retVal[0]*value) + " - " + (retVal[1]*value); }


  • Create a string for showing the amount of effort involved for a given amount
  • of hours. It attemps to be fairly elegant in the returned value.
  • If their is only one unit to show, it will use the full pluralized unit name.
  • Otherwise, it will use "1 w 2 d" etc. */ function EFFORT(hours, frac) { if (frac == undefined) frac = false; var retArr = []; if (hours < hoursPerDay) { return hours + " hour" + (hours > 1 ? "s" : ""); } else if (hours < hoursPerWeek && frac) { days = (hours / hoursPerDay); return days + " day" + (days > 1 ? "s" : "");
    } else if (frac) { weeks = (hours / hoursPerWeek); return weeks + " week" + (weeks > 1 ? "s" : "");
    else { var weeks = parseInt(hours / hoursPerWeek); hours %= hoursPerWeek; var days = parseInt(hours / hoursPerDay); hours %= hoursPerDay; if (weeks > 0) { retArr.push(weeks + " " + ((days == 0 && hours == 0) ? "week" + (weeks > 1 ? "s" : "") : "w")); } if (days > 0) { retArr.push(days + " " + ((weeks == 0 && hours == 0) ? "day" + (days > 1 ? "s" : "") : "d")); } if (hours > 0) { retArr.push(hours + " " + ((weeks == 0 && days == 0) ? "hour" + (hours > 1 ? "s" : "") : "h")); } return retArr.join(" "); } }
Continue reading...