Tuesday, July 22, 2014

Cheap VHF Communications

The high power rocketry club I am a member of uses two meter amateur radios for communications. We do this on the drive to the launch range and while walking around and doing ground crew duties as well.  My money has a variety of places to go right now, so for the time being I needed to simply find the least expensive setup that works.  I've found that.  Serious amateur radio enthusiasts probably won't care much for my choices, but from a purely utilitarian perspective they work well enough for me, at least for the time being.

Sorry, the radio still has desert all over it.

The transceiver itself is the (in)famous Baofeng UV5R-A.    A lot of the complaints people have about the menus being poorly laid out are frankly true.  With that said, when I talk into the radio people hear me and are able to understand what I said.  I am also able to understand them.  I programmed the repeaters in my area into it with CHIRP, bypassing the obnoxious menu.






One common (and in my experience valid) complaint about the Baofengs is the shit-tacular antenna they come with.  I picked up a knockoff of a Nagoya antenna from eBay for about $5 a while back (they don't seem to be available any longer).  After doing this, I was able to pick up a lot more traffic than I could previously.

For just walking around, this is really all that you need.  However, if you want to use your radio in a vehicle this won't quite do it.

This is a Nagoya magnetic mount antenna.  It has a reasonably strong magnet at the bottom, which sticks to the roof of my truck quite nicely.  I simply ran the wire in through the passenger side wing window.



Picking up the transceiver while driving and it having a big wire coming off of the end of it is no fun, but fortunately a remote microphone/speaker combo is available.  Some users have complained about poor audio quality from the speaker, and of others not being able to hear them well when speaking.  Personally, I've had no problems with bad received audio quality.  I have personally had no problem with the received audio quality.  With that said, I have noticed that I need to speak directly into the microphone to be heard well.

I don't want to risk draining the battery while on a long drive, so I picked up a cigarette lighter adapter.  It is clearly made from a hollowed out battery (it even says "Li-ion Battery" on the thing), but despite its chintzy appearance, it works.  Some users have complained about their radios heating up when they use it, but I haven't experienced this.

This gear allowed me to communicate with other people a few miles away.  It is true that I had some issues while crossing the Cascades (line of sight stuff), I wouldn't be surprised if people with nicer radios had similar problems.  At the end of the day, the Baofeng gives you a lot to work with for very little money.  I would like to get something nicer at some point, but this will do for the time being.


Tuesday, May 28, 2013

KE Jetronic Fuel Mixture Adjustment

In the mid 1970s, Robert Bosch GmbH developed a mechanical fuel injection system called K (konstant) Jetronic.  It was a very clever system, which could compensate for variables like engine temperature and air density without the use of electronics.  As time marched on and emissions requirements became more strict, the folks at Bosch added electronic controls to the system to extend its life.  This was called KE Jetronic.

My daily driver (a W124) is equipped with KE-Jet.  Recently I began having problems with the spark plugs fouling.  The car doesn't burn oil, and various sensors used by the fuel system tested okay.  Some research lead me to discover that as these cars age, they end up running too rich.  The fuel distributor can be adjusted to correct the problem.  The problem with this is that federal regulations required anti-tamper equipment be put in place to keep the fuel mixture from being trivially adjusted.  I suppose there was a belief that some shithead hammer mechanic would think they could get more power out of their car by dumping more gasoline into the engine.  Fortunately, it is pretty easy to get around the anti tamper stuff.


Here we see the throttle body and fuel distributor.  The little metal tower at the base of the fuel distributor covers the adjustment screw.  The cap at the top of it is actually an extremely thin piece of metal.  If you drill through it slowly and carefully, you'll find a small metal disk and a couple of pieces of felt.  Under that is the mixture screw.

After you drill through this, reassemble the air cleaner box.  Start the car, allow it to warm up, and connect a multimeter in duty cycle mode to the X11 diagnostic connector.  The negative lead goes to pin 2, and the positive to pin 3.  There is a small hole in the top of the air cleaner box.  Carefully insert a long #3 Allen bit into this hole.  Pressing down and turning will adjust the mixture (counter clockwise is lean, clockwise is rich).  Make your adjustments slowly, no more than a quarter turn at a time.  You will want to wait a little while between each adjustment.  The initial article I read said you wanted to wait at least 10 seconds.  I found it to be more like 30.  When the duty cycle is alternating between 45% and 55% (it will move), it is set properly.

Since making this adjustment, I have found that my car idles much better.  I expect to see improved fuel economy (since I'm not sending unburned fuel out the exhaust pipe) too, but time will tell on that one.

This is yet another example why I consider attempts to restrict access to a person's own property to be hostile.  I have a right to repair my car.  The anti-tampering stuff was put in place with the assumption that I am some kind of moron who would dump more gasoline than can be burned into the engine.  Bypassing this  created a small but real risk of drilling through something other than what I intended to.  The risk paid off for me, but what if it didn't?

Sunday, March 24, 2013

ChibiOS, lwIP, UDP, and You

Introduction
It certainly has been a while since I posted anything.  I increased my course load in hopes of actually finishing college some day, and have had some family issues which ate up a great deal of my time. Hopefully, this post will be interesting enough make up for that fact.

I have recently joined a high power rocketry club, and am involved in the development of avionics software for the rocket.  Since I don't work in aerospace (telecom guy here) and know very little about embedded software development, I have been scrambling up the learning curve.

The club I have joined is in the process of converting their flight control systems from communicating over USB to ethernet.  The goal for this is reduced latency and better throughput.  We're using a bunch of Olimex STM32-E407 boards to relay sensor data back to the flight computer and to control various actuators.  The boards run ChibiOS/RT.  IP stuff is handled by lwIP.  I'm going to spend a little bit of time going over what has to be done in order to get everything up and running.  The code I've been working on does other stuff in addition to networking, so I'm just going to be posting network related snippets, instead of just doing a code dump.  If I miss something, I sincerely apologize.

main.c
The high level logic of the application lives in main.c.  The following includes are added:


#include <lwip/ip_addr.h>
#include "data_udp.h"
#include "lwipopts.h"
#include "lwipthread.h"


The <lwip/ipaddr.h> and "lwipthread.h" headers are part of lwIP and have not been modified.  The data_udp.h file contains function prototypes and defines for UDP stuff that comes later.  The lwipopts.h header contains the configuration for lwIP.  Due to the simplicity of ChibiOS, all configuration is done at compile time, so you will probably want to edit that file.

The other relevant stuff in main.c is in the main() function.  Here, we declare the lwip configuration and  configure the ethernet interface on the board:
          struct lwipthread_opts   ip_opts;
    static       uint8_t      macAddress[6]    =     {0xC2, 0xAF, 0x51, 0x03, 0xCF, 0x46};
    struct ip_addr ip, gateway, netmask;
   IP4_ADDR(&ip,      10, 0, 0, 2);
   IP4_ADDR(&gateway, 10, 0, 0, 254);
   IP4_ADDR(&netmask, 255, 255, 255, 0);
   ip_opts.address    = ip.addr;
   ip_opts.netmask    = netmask.addr;
   ip_opts.gateway    = gateway.addr;
   ip_opts.macaddress = macAddress;


Now we fire off a thread for the UDP listener.  We are using a nice 32 bit ARM chip, so we can have multiple threads doing multiple things on our board.  Since we  have a metric shitton of GPIO pins and will probably want to do stuff to more than one of them at a time, I think this is the way to go.

chThdCreateStatic(wa_data_udp_receive_thread, sizeof(wa_data_udp_receive_thread), NORMALPRIO, data_udp_receive_thread, NULL);


data_udp.c
In this one, we again have some includes:
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/ip_addr.h"

#include "data_udp.h"

All of these except data_udp.h are standard lwIP stuff.  The data_udp.h header has some defines that we need to get stuff working that I'll cover shortly.

First we set up a working area for the receive thread that gets started in main().
WORKING_AREA(wa_data_udp_receive_thread, DATA_UDP_SEND_THREAD_STACK_SIZE);


This function spins in the thread we created.  It spins, and lets the function data_udp_rx_serve() handle what packets come in.
msg_t data_udp_receive_thread(void *p) {
  void * arg __attribute__ ((unused)) = p;

  struct netconn *conn;

  chRegSetThreadName("data_udp_receive_thread");

  chThdSleepSeconds(2);


  IP4_ADDR(&ip_addr_fc, 10,0,0,2);
  /* Create a new UDP connection handle */
  conn = netconn_new(NETCONN_UDP);
  LWIP_ERROR("data_udp_receive_thread: invalid conn", (conn != NULL), return RDY_RESET;);

  netconn_bind(conn, &ip_addr_fc, DATA_UDP_RX_THREAD_PORT);

  while(1) {
    data_udp_rx_serve(conn);
  }
  return RDY_OK;
}


The data_udp_rx_serve function looks like this:
 static void data_udp_rx_serve(struct netconn *conn) {
  BaseSequentialStream *chp =  (BaseSequentialStream *)&SDU1;
  struct netbuf   *inbuf;
  struct pbuf *buf;
  char cmdbuf[64];
  uint16_t        buflen = 0;
  uint16_t        i      = 0;
  err_t           err;
  /*fill buffer with nulls*/
  for (i = 0; i < 64; i ++) {
    cmdbuf[i] = 0;
  }
  /* Read the data from the port, blocking if nothing yet there.
   We assume the request (the part we care about) is in one netbuf */
  chprintf(chp, ".w.\r\n");
  err = netconn_recv(conn, &inbuf);
  chprintf(chp, ".+.\r\n");
  if (err == ERR_OK) {
    /*netbuf_data(inbuf, (void **)&buf, &buflen);*/
     /*int bytesCopied = netbuf_copy(inbuf, (void **)&buf, &buflen); */
    int bytesCopied = netbuf_copy(inbuf, cmdbuf, 64);
   chprintf(chp, "\r\ndata_udp_rx: %s", cmdbuf);
    chprintf(chp, "\r\n");
    chprintf(chp, "copied %d bytes\n", bytesCopied);
    if (strncmp("GETPWMWIDTH", (const char *) cmdbuf, 11) == 0) {
        char respBuf[64];
        unsigned int pulseWidth = getPulseWidth();
        sprintf(respBuf, "PULSE WIDTH %d", pulseWidth);
        sendResponsePacket(respBuf);
    } else {
        sendResponsePacket("CMDUNDEF");
    };

  }
  /*fill buffer with nulls*/
  for (i = 0; i < 64; i ++) {
    cmdbuf[i] = 0;
  }
  netconn_close(conn);

 
  /* Delete the buffer (netconn_recv gives us ownership,
   so we have to make sure to deallocate the buffer) */
  netbuf_delete(inbuf);
}

Essentially, we block until we read a UDP packet.  After that we copy it out into a buffer and do a standard string comparison against the buffer's contents.  We respond to the contents by sending another packet with sendResponsePacket().


 void sendResponsePacket( char  payload[]) {
   struct     netconn    *conn;
   char                   msg[DATA_UDP_MSG_SIZE] ;
   struct     netbuf     *buf;
   char*                  data;
    struct ip_addr addr;
    addr.addr = UDP_TARGET;
   conn       = netconn_new( NETCONN_UDP );
   netconn_bind(conn, NULL, 35001 ); //local port

   netconn_connect(conn, &addr , DATA_UDP_REPLY_PORT );
   buf     =  netbuf_new();
   data    =  netbuf_alloc(buf, sizeof(msg));
   sprintf(msg, "%s", payload);
   memcpy (data, msg, sizeof (msg));
   netconn_send(conn, buf);
   netbuf_delete(buf); // De-allocate packet buffer
    netconn_disconnect(conn);
}

 
This essentially works as expected.  We create a new connection, and set our source port as 35001.  We then fire a packet off to the target, and call it a day.

data_udp.h
There are few interesting things in this file.  The port numbers are simply added as #define s, as are the stack sizes.  The only thing really worth mentioning is that the IP addresses must be defined in hex, and stored in network bit order.  The former can be accomplished by writing the hex representation of each octet in order, without the periods.  The latter is handled by the htonl() function:
 #define UDP_TARGET                                (htonl(0xA000001))        //10.0.0.1


Conclusion 
I'm a complete noob when it comes to embedded things, and a bit rusty with regard to C.  None the less, I seem to be able to get this board doing stuff.  Hopefully there will be epic rocket fun in my future.

Thursday, November 15, 2012

Product Review - Ikea Sunnan Solar Desk Lamp

Much like Harbor Freight Tools, Ikea is a business that I have mixed feelings about.  Both sell products at affordable prices.  Sometimes the quality is good enough to get the job done, sometimes it's surprisingly high, and sometimes you end up with the piece of shit you paid for.  Either way, they both fit into an important niche in the economy.
Recently Ikea has begun to sell some solar desk lamps called the Sunnan.  I received two of them for my birthday.  Incidentally - if you buy a Sunnan, another is donated to Unicef.  The idea behind this is that people in developing countries that don't have electricity can use their solar lamps instead of candles and oil lamps, thereby leading toward fewer house fires.
I'd say the build quality is on the high end of average for Ikea stuff.  It's made of your standard molded plastic, but it is pretty thick.  Since it is a desk lamp, this should be more than adequate.  The part that actually emits the light is mounted on a flexible stalk.

As shown below the light output from the thing is far from fantastic:


Given that the thing runs off three AA batteries, this is to be expected.  I'm no electrical engineer, but I can tell you that emitting more light would use more power, and power is a product of voltage and current.  AA batteries can only move so much current for so long.

The charging system for the lamp is pretty novel.  The battery pack + solar panel is a module that sits in the base of the lamp.  It can be removed and placed in a sunny spot to charge.


Given that I live in Oregon and it is November, "sunny" is a relative term.  Regardless, after spending the day in the window the battery packs charged enough to run the lights again for some time.

The Sunnan is an interesting product.  It isn't going to come close to replacing most of the lighting in my house, even my non-solar desk lamp.  I'm probably going to keep using it when I'm goofing around on the computer at night.  It almost feels like a proof of concept - maybe a subsequent version will be made that uses C or D cells and is brighter. 

There's also some immediately obvious hackability.  The way that the battery pack mounts into the base looks compatible with the blade terminal connectors often used in automotive wiring.  It'd be pretty easy to remotely mount the battery pack, or repurpose it for something else.

I guess you could say I'm glad I have these lamps, but I'm glad I didn't buy them either.  For every day use, they just aren't quite there.  With that said, if I was subject to an extended power outage or lived in a flavela with no electricity I would probably feel very different.

Sunday, November 11, 2012

Getting Java to talk to MySQL

Java is a very powerful,  popular, annoyingly verbose object oriented programming language. 

I don't use Java at work or for personal programming (LAMP stuff does what I need there) but I'm slowly chipping away at my CS degree, and I found myself in the middle of a Java project for which a database was the only non-stupid solution.  Figuring out how to get it to talk to MySQL was a pretty easy process.  I'm documenting my results here.



Importing the Libraries
The needed libraries are in the java.sql.* part of the class heirarchy.  You'll need to simply put:
import java.sql*;
at the top of your code.  You'll also need to either have mysql.jar in your CLASSPATH or add it to your build path.  Eclipse can handle this automatically for you, and I'm willing to bet most other IDEs can do the same.

Connecting to the Database

Creating a connection to the database is quite simple.  You simply need to know the host on which MySQL is running, the name of the database, and the username and password.  The example below illustrates this:

try {
            String host = "127.0.0.1";
            String dbName = "baseOfData";
            String username = "sqlUsername";
            String password = "sqlPassword"; 
            String hostUrl = "jdbc:mysql://" + host + '/' + dbName;
            dbConn = DriverManager.getConnection(hostUrl, username, password );       
        } catch (SQLException ex) {
            //Handle the errors
            System.out.println("SQLException: " + ex.getMessage());
            System.out.println("SQLState: " + ex.getSQLState());
            System.out.println("VendorError: " + ex.getErrorCode());
        }

If connecting too the database fails, it will throw an SQLException.  The code in the catch block should print out enough information for you to figure out what went wrong.

Executing Queries
You can prepare and execute queries from Java, just like any other programming language.    Simply building and executing a query looks like this:

PreparedStatement sqlStatement = dbConn.prepareStatement(
                    "select p.id, p.name, p.address, p.city, p.state, p.zip, sum(s.cost) " +
                    "from services_provided sp join providers p on p.id = sp.provider_id " +
                    "join services s on sp.service_id = s.id group by p.id"
                    );
ResultSet results = sqlStatement.executeQuery();


Please note that this (and all examples) should be done in a try block.

Java supports bind variables as well.  To use bind variables you build the query with wildcards, set them, then run the query as shown below:

PreparedStatement sqlStatement = dbConn.prepareStatement(
                "update services set name = ?, cost = ? where id = ?"   
);
sqlStatement.setString(1, serviceName);
sqlStatement.setFloat(2,cost);
sqlStatement.setFloat(3, id);
sqlStatement.execute();


Getting Data Back
Getting data out of the database is quite straight forward as well.   You create a result set from the query output, which you can iterate through.  The results object will have a variety of get methods (getInt, getString, etc) for extracting the needed data as shown below:

PreparedStatement sqlStatement = dbConn.prepareStatement("select * from services");
ResultSet results = sqlStatement.executeQuery();
while (results.next()) {
                Map <String,String> service = new HashMap<String,String>();
                service.put("id", Integer.toString(results.getInt(1)));
                service.put("name", results.getString(2));
                service.put("cost", Float.toString(results.getFloat(3)));
                services.add(service);
}


Conclusion
Getting data in and out of MySQL in Java is pretty much like it is everywhere else, except more heavily object oriented than in most languages I am used to dealing with.  Despite their differences, programming languages are all more or less the same.  This is why I think it is important to focus on concepts, rather than implementations.  

Sunday, September 16, 2012

RTL2832U SDR Logging Tool

A while back I posted about the fact that some people far smarter than me learned that some USB TV tuner dongles could be used as software defined radio receivers.

This weekend I finally had time to cook up some code that used the dongle to sweep across a given range of frequencies and record the strength of the signals received.

There are two parts to the tool.  The first is the logger, which does the actual reading and storage of the data.  The second is the plotter, which creates a graphical representation of the data points which have been stored.  We will first start with the plotter.

redacted@awesomecomputer:~/sources/pyrtlsdr$ ./freqLogger.py -h
usage: freqLogger.py [-h] [-s START_FREQUENCY] [-e END_FREQUENCY]
                     [-g GAP_START] [-f GAP_END] [-i INCREMENT]
                     [-d DESCRIPTION] [-m MINUTES]

optional arguments:
  -h, --help            show this help message and exit
  -s START_FREQUENCY, --start_frequency START_FREQUENCY
                        starting frequency for sweep
  -e END_FREQUENCY, --end_frequency END_FREQUENCY
                        ending frequency for sweep
  -g GAP_START, --gap_start GAP_START
                        start of band gap
  -f GAP_END, --gap_end GAP_END
                        end of band gap
  -i INCREMENT, --increment INCREMENT
                        frequency increment for loop
  -d DESCRIPTION, --description DESCRIPTION
                        Description for report
  -m MINUTES, --minutes MINUTES
                        Number of minutes to run scan
redacted@awesomecomputer:~/sources/pyrtlsdr$




If started with no options, the logger simply loops through those frequencies (in 1 MHz increments) which it can tune to, and logs the signal strength in the database.  Please be aware that these values are based upon what my dongle can do, and they vary somewhat.  The frequency ranges and band gap can all be set via command line arguments (or by simply editing the file, since it is written in Python).  If I wanted to scan 162 through 174 MHz for 20 minutes  I would simply run:

 redacted@awesomecomputer:~/sources/pyrtlsdr$ ./freqLogger.py -s 162 -e 174 -m 20
Found Elonics E4000 tuner


The script would  run for 20 minutes, then stop.

The plotter is quite easy to use as well.  Its options are as follows:

redacted@awesomecomputer:~/sources/pyrtlsdr$ ./reportPlotter.py -h
usage: reportPlotter.py [-h] [-r REPORT_ID] [-l] [-t] [-m MIN_FREQ]
                        [-n MAX_FREQ] [-s SAVE]

optional arguments:
  -h, --help            show this help message and exit
  -r REPORT_ID, --report_id REPORT_ID
                        report id to plot
  -l, --list_reports    list reports
  -t, --time_plot       plot signal strength vs time
  -m MIN_FREQ, --min_freq MIN_FREQ
                        minimum frequency for graph
  -n MAX_FREQ, --max_freq MAX_FREQ
                        maximum frequency for graph
  -s SAVE, --save SAVE  save graph to file (png)

Each run of the logger is listed as a report.  To list the reports currently stored in the database, execute the plotter with the -l switch.

redacted@awesomecomputer:~/sources/pyrtlsdr$ ./reportPlotter.py -l
Report Id   Minimum Frequency   Maximum Frequency  Increment   Bandgap Minimum   Bandgap Maximum Description
       16          162.000000          174.000000   1.000000       1089.000000       1252.000000 Generic Report     
       15          406.000000          420.000000   1.000000       1089.000000       1252.000000 Generic Report     
       14          406.000000          420.000000   1.000000       1089.000000       1252.000000 Generic Report     
       13          406.000000          420.000000   1.000000       1089.000000       1252.000000 Generic Report     
       12          406.000000          420.000000   1.000000       1089.000000       1252.000000 Generic Report     
       11          406.000000          420.000000   1.000000       1089.000000       1252.000000 Generic Report     
       10          566.000000          574.000000   0.001000       1089.000000       1252.000000 Scan 566 to 774    
        1           52.000000         2176.000000   1.000000       1089.000000       1252.000000 initial scan     
  

 Two types of reports are supported, signal strength vs frequency, and signal strength vs time.  Please be aware that strength vs time is probably only useful for a very narrow range of frequencies.

Signal Strength vs Frequency
Signal Strength vs Time
The code can be downloaded here.
In closing I must say that I actually do not know anything about digital signal processing at all.  The method I use to read the signal strength is from a script found here.    None the less, areas where high signal strength are reported correspond to known broadcasts, so I think this is correct information.  This is also one of the first actually useful things I have ever done with Python, so it is highly likely that I have done some things that would be frowned upon by the Python community.  Either way, I think I have put something useful together.




White IPA - Taste Test

The white IPA I blogged about previously is now drinkable.  It isn't half bad either.

There's a nice citrus taste there because of the orange peel, as well as the "grassy" taste common to a lot of Belgian beers.  Unfortunately I think I overdid the hops a little.  I'm going to try backing off on the AAUs a little bit next time.  I'm also thinking about trying a couple of varieties of bittering hop to add some complexity.

Regardless, I think its pretty good for the first iteration of a new recipe.