Saturday, June 13, 2015

Simple pump from a juice bottle

In some cars, the only way to add or check transmission fluid is to remove a hard-to-reach plug and add fluid until it starts pouring out the hole.  Since the hole is in the side of the transmission, there's no way to get a funnel into the hole.  You have to pump the fluid into the hole.

Auto parts stores will sell you a pump, but I discovered that all I needed was a water bottle and some tubing.  Drill two holes in the cap for the tubes, fill the bottle about halfway with fluid and force air into the short tube.  I was worried about air leaking between the hole and the tubing, but it came out just right, nice and tight.  It can still work without a perfect seal but it'll require more air.

This tubing was the perfect diameter to thread into the nozzle of the air gun on my compressor, but I think it would have worked just as well to simply blow into the short tube.  (I tried it with water just now and it seemed to work fine, but watch out for fumes and don't let any fluid come back up the short tube!)

The tubing shown here is for the ice maker in my fridge.

Thursday, May 28, 2015

Mary Meeker Internet Trends 2015

Slides of interest to me:

p.47: 6 of top 10 mobile apps are for messaging
p.68: Social network use in 12-24 year olds: Instagram 32%,Twitter 24%, Facebook 14% (down from 35% in 2013)
p.69: 78% of millennials spend >2h per day on smartphone
p.70: 44% of millennials use smartphone camera at least once / day
p.103: Americans receiving government benefits 50% in 2012 vs. 30% in 1983.
p.110: #2 top work value for millenials is "Flexible Working Hours"
p.113: Millenials seen as more narcissistic, open to change, creative than Gen X
p.117: USA Smartphone penetration 64%
p.132: 72% of NYC airbnb hosts depend on it for rent/mortgage
p.163: 7% of Xaomi phone buyers (China) buy a Xaomi home product
p.165: India starting to take off in Internet penetration (very cool chart)
p.169: India #2 in % of internet traffic via mobile
p.170: 41% of India e-commerce is via mobile

Monday, March 23, 2015

Steam Linux audio problems

No audio at all from steam games?  Try 'pavucontrol'.  It'll show you when games are trying to play audio and where they're playing it to. In the "configuration" tab I had to disable built-in audio and switch my "HDA NVidia" device to "Digital Surround 5.1 (HDMI) Output" from "Digital Stereo (HDMI) Output".

Sunday, March 22, 2015

GQL to CSV exporter for Google App Engine

The App Engine admin panel will let you run GQL queries against your datastore, but it won't let you download the results as a CSV. So I wrote a [very] quick and [very] dirty handler that does, which also has the handy side effect that you can put your own access controls on it. That means you can give someone on your team read-only access to your datastore without also giving them access to the admin panel.

If you're using db instead of ndb,  I believe the main thing to change is ndb.gql() to GqlQuery().

 # Very quick and dirty example of how to provide unfettered read  
 # access to your datastore with export to CSV. Be sure to add appropriate  
 # access controls and watch out for security risks (like XSS)  
 # Don't forget to:  
 # from google.appengine.ext import ndb  
 # from google.appengine.ext.ndb import metadata  
 class GqlPage(webapp2.RequestHandler):  
  def get(self):  
   limited = True  
   row_limit = 1000  
   # Tricky to distinguish absence of 'limit' checkbox when you  
   # first hit the URL from when you submitted with an unchecked box  
   if self.request.get('download', 'nope') != 'nope' and \  
     self.request.get("limit", "nope") == "nope":  
    limited = False  
   query = self.request.get('query', "empty")  
   is_csv = False  
   if self.request.get('download') == 'Download':  
    is_csv = True  
    self.response.headers['Content-Type'] = "text/csv"  
    self.response.write('<form action="/gql" method=POST>')  
    self.response.write('<textarea name=query rows=10 cols=80 placeholder="select * from...">')  
    if query != "empty":  
    self.response.write("<input type=submit name=download value=View>")  
    self.response.write('<input id=foo type=submit name=download value=Download')  
    self.response.write(' onclick="document.getElementById(\'results_div\').innerHTML=\'\';">')  
    self.response.write("Limit response to " + str(row_limit) + " rows:")  
    self.response.write("<input name=limit type=checkbox ")  
    if limited:  
    self.response.write("Examples for available tables:<br>")  
    for kind in metadata.get_kinds():  
     self.response.write("select * from " + kind + "<br>")  
    self.response.write('<br><div id="results_div"><pre>')  
   if query != 'empty' and query != '':  
    results = []  
    if limited:  
     results = ndb.gql(query).fetch(row_limit)  
     results = ndb.gql(query).fetch()  
    writer = csv.writer(self.response.out)  
    row_count = 0  
    first_row = True  
    for row in results:  
     row_dict = row.to_dict()  
     keys = sorted(row_dict.keys())  
     # Write column labels as first row  
     if first_row:  
      first_row = False  
     values = []  
     for k in keys:  
      value = str(row_dict[k])  
      if is_csv:  
     row_count += 1  
     if not is_csv and limited and row_count == row_limit:  
      self.response.write("\n[Truncated at " + str(row_limit) + " lines]")  
   if not is_csv:  
  def post(self):  
   return self.get()  

Friday, March 20, 2015

Linux data acquisition with an Agilent Infiniium MSO9404A oscilloscope

I need to capture some analog data.  My coworker has a National Instruments USB data acquisition module, but as far as I can tell, the drivers are all proprietary and focused on using Labview.

So instead I used a fancy 9000-series Agilent Infiniium scope.  I plugged my laptop's ethernet cable into the scope, then used the Windows control panel on the scope to set its IPv4 address to  I set my laptop to, and found that I could ping the scope.  So far so good.

Back in the day, test equipment was controllable over a serial GPIB bus.  Today's fancy gear uses the same conventions over TCP/IP.  There's a confusing mess of acronyms like VXI-11 and VISA, and a bunch of half-baked libraries and crappy-looking system drivers that appear to be required to use them.  pyvisa looks nice, but wants a proprietary National Instruments driver distributed as a .iso(!).  Not my cup of tea.

Then I ran across this MATLAB example that's basically just chatting with the device over TCP/IP. That led me to Agilent's Programmer's Reference guide for the 9000-series scopes.

After fighting with the 1100-page manual for a few hours, I came up with the following settings that let me get samples at a specific rate like I would from an ADC.

Note that you can also interactively talk to the scope using nc or telnet to port 5025 while you're experimenting.

 # Using ethernet to talk to an Agilent Infiniium MSO9404A  
 # See also the "Infiniium 9000A Programmer's Reference"  
 import socket  
 import sys  
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 sock.connect(("", 5025))  
 def cmd(s):  
  sock.send(s + "\n")  
 def resp():  
  r = sock.recv(1048576)  
  while not r.endswith("\n"):  
   r += sock.recv(1048576)  
  return r  
 print "Querying scope"  
 print "Scope identifies as: ", resp(),  
 # This doesn't seem to affect the samples we receive  
 cmd(":timebase:range 1E-6")  
 cmd(":timebase:delay 0")  
 cmd(":channel1:range 5")  
 cmd(":channel1:offset 2.5")  
 cmd(":channel1:input dc")  
 cmd(":trigger:mode edge")  
 cmd(":trigger:slope positive")  
 cmd(":trigger:level chan1,2.5")  
 cmd(":system:header off")  
 cmd(":acquire:mode rtime") # Realtime mode; don't average multiple passes  
 cmd(":acquire:complete 100")  
 cmd(":waveform:source channel1")  
 cmd(":waveform:format ascii")  
 cmd(":acquire:count 1")  
 cmd(":acquire:srate:auto off")  
 cmd(":acquire:srate 4000")  
 cmd(":acquire:points:auto off")  
 cmd(":acquire:points 16")  
 # This was on by default, and took me a long time to figure out why  
 # I was getting ~16x the number of samples I requested.  
 cmd(":acquire:interpolate off")  
 cmd(":digitize channel1")
# This should block until the capture is done, since we used :digitize
 sample_string = resp().rstrip().rstrip(",")  
 ascii_samples = sample_string.split(",")  
 samples = []  
 for f in ascii_samples:  
   print "Couldn't convert:", f  
 print "Got ", len(samples), " samples. Values 1-10:", samples[:10]  

Monday, February 16, 2015

Empirically testing the "three hats game"

After reading the brain teaser here, I couldn't convince myself that the solution was correct:
It still seems impossible to me that any player should be able to do better than 50% at guessing the color of his own hat. So I wrote a C program to try out the strategy across 10000 games and report what it found. And indeed it does seem to work. I'd paste the code, but blogger sucks at code formatting. So here it is instead:

Friday, February 06, 2015

Ubuntu: use syndaemon disable touchpad while typing

As this page describes, syndaemon solves the problem of trying to type on a laptop and having the cursor jump somewhere else because it thinks you touched the touchpad: