Posts From January 2015 - Musing, Rants & Jumbled Thoughts

Header Photo Credit: Lorenzo Cafaro (Creative Commons Zero License)

This article is one in my series focusing on what I learned creating a Windows Installer using the WiX toolkit’s "Managed Bootstrapper Application" (Custom .NET UI) framework. Each post builds upon the previous posts, so I would suggest you at least skim through the earlier articles in the series.

Here’s an index into the articles in this series:

During an installation, it's often expected that the end user can change the installation folder from the default value. Obviously, you'll need some UI controls to allow the user to select the destination folder they want to use, but what do you do with it once you have it?

Changing the install folder location for your installation has two main tasks:

  • Overriding the folder path at install
  • Determining the override used at modify/uninstall/upgrade time

I'll walk through both in this article.

Overriding the Install Folder Path

Let's look at how the default install folder is set. In your individual MSI WiX projects, you'll have a Directory fragment defined that will look something like this:

  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="MYINSTALLFOLDER" Name="Wrightfully Blog Examples">

          <!-- ... subfolders -->   
          <Directory Id="dirLicenses" Name="Licenses" />
          <Directory Id="dirdoc" Name="doc" />
        </Directory>
      </Directory>
    </Directory>
  </Fragment>

Notice that some of the Ids are in ALLCAPS. This means they are exposed as variables that can be set by the WiX Engine's Engine.StringVariables array. The override will replace that node in the structure and all child folders will branch off the new path. Overrides will not affect any Directory nodes defined above or beside it in the xml structure -- only it and it's children.

So, if I want to override the install folder defined in my Directory fragment, I'd do something like this:

var pathOverride = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                           "My Alternate Location");

Engine.StringVariables["MYINSTALLFOLDER"] = pathOverride;

Then, when the actual MSIs install, they will substitute the value for MYINSTALLFOLDER to use my new value at install time. But first, you have to tell WiX to pass the MYINSTALLFOLDER along to the MSI, which you do in the <MsiPackage> declaration of your <Bundle>, like this:

  <MsiPackage Compressed="yes" SourceFile="$(var.MyMis.TargetPath)">
      <MsiProperty Name="MYINSTALLFOLDER" Value="[MYINSTALLFOLDER]"/>
  </MsiPackage>

Determining Override Used in Later Runs

Unfortunately, if you override the folder at install time, that's not persisted by the installation system, so you don't know what the value was set to during later runs -- such as when the user clicks Modify/Uninstall in Add/Remove Programs, or when the user goes to upgrade with a newer version of the installer.

So, in order to correctly re-override the value during future runs, we need to store it somewhere and then go grab that value when we run. And we do this with a registry key. You could do this directly in the Bootstrapper Application (managed code), but I prefer to use the WiX functionality to manage the registry key and ensure it's removed when the software is uninstalled, etc. So in an MSI Feature that will always be installed with my software, I put this in my product definition:

<Feature Id="feat_Documentation" Title="Documentation" Level="1" Display="hidden">
    <ComponentGroupRef Id="compgroup_Docs" />

    <Component Directory='MYINSTALLFOLDER'>
        <RegistryValue Root='HKLM' Key='SOFTWARE\\Wrightfully\\BlogInstaller'
                   Name='MyInstallFolder' Value='[MYINSTALLFOLDER]'
                   Type='string'></RegistryValue>
    </Component>
</Feature>

This will create the registry key and populate it with the value of MYINSTALLFOLDER. Then, in my bootstrapper app, I can read the values using this code:

var existingInstallLocation =
    Registry.GetValue(@"HKEY_LOCAL_MACHINE\\SOFTWARE\\Wrightfully\\BlogInstaller",
                    "MyInstallFolder", "").ToString();
if (!string.IsNullOrEmpty(existingInstallLocation))
{
     Engine.StringVariables["MYINSTALLFOLDER"] = existingInstallLocation;
}

If the registry value exists (is not an empty string), then I use that value for [MYINSTALLFOLDER]. Otherwise, I use the default value.

TIP: Since the default value for MYINSTALLFOLDER isn't known by the Bootstrapper App (it's defined in each MSI's Directory fragment), I just always set it to a default value at startup of my bootstrapper (or to the override found in the registry) -- which means the value in the Directory fragments is never actually used.

ANOTHER TIP: Make sure you do some general validation on the user-entered values to make sure it's a valid path. Otherwise, the MSI installs will blow up, and you definitely don't want to deal with that.



It's the first week of January, so I am required by Internet law to post a "year in review" for 2014... so here we go.

"Looking Back" photo attribution: Some rights reserved by Alberto OG

This year has been an interesting and good one - and jam-packed. Here's a rundown of some of the major hightlights for me.

Joined The That Conference staff

In 2013, I attended That Conference, a technical conference in the Wisconsin Dells. And I really enjoyed the experience. So much so, that I contacted the organizers and offered to help out for 2014. On Jan 10th, I became a member of the team.

One of the main goals for That Conference 2014 was to rewrite the website, and I ended up taking lead on that project. A lot of commuting hours on the laptop, late nights, team video calls and "what the hell did I get myself into" moments later, I had come up to speed on ASP.NET MVC, Bootstrap, OAuth, Git and GitHub, AngularJS, Twilio and a whole host of other technologies I had little or no experience with.

I also learned a great deal about the business and logistical side of putting together a conference for 1000+ attendees and their families. And doing so with a team of 100% volunteers. During the week on-site, it was exhilarating and exhausting work. And while InRule brought our entire dev team to attend and were Platinum sponsors for the event, I barely saw them as I was running around helping keep this crazy train from coming off the tracks.

I met a ton of great people and am already getting rolling for 2015.

Shameless Plug: Looking for a great technical conference in the Midwest? Come join 1000+ motivated professionals for three days (Aug 10-12, 2015) for 125+ exciting sessions on web, mobile, cloud and everything else, across a variety of languages and platforms. Plus, it's held at a water park (the Kalahari Resort in the Wisconsin Dells), so bring your family -- there's family track with sessions for them too! That Conference

New Job Title: Director of Development

My role at InRule Technology changed a bit this year, as I became the Director of Development. I documented this in my aptly-name post I Have A New Job Title: Director of Development, so won't go much further here.

Along those lines, however, one thing to note is that InRule saw a lot of growth this year. In particular, we ended the year with 11 new employees that weren't here at the start of the year. And while we did lose a couple people as well, that's a pretty big number for a company of around 40 people! We were also named to Inc. 500|5000 List of Fastest Growing Companies at #3636, and made the list of Chicago's 101 Best and Brightest Companies to Work For!

Septoplasty

For as long as I can remember (going back 20 or so years), I've had a screwed-up nose which has made it difficult to breath through it. I, however, am quite the coward when it comes to medical procedures, so have aggressively avoided doing anything about it. But this year, which much encouragement from my wife, I finally got around to having it fixed and in early summer had a Septoplasty. While it was not a fun experience, I'm glad I did it. Though, I'm still getting used to what it feels like to have air (especially cold, winter air) flow through my sinuses -- something that hasn't really happened for several decades.

First International Vacation

Along with my wife and son, I took my first international vacation this summer -- as well as our first "destination vacation" as a family. We headed south to the Atlantis Resort in the Bahamas and it was great!

Son Back In Public School

My son, who has some substantial learning disadvantages, had not been well served by the public school system for the first few years, so we made the decision to home school him a few years back. This year, however, we found a program within the public school that matches up with his needs (and has staff that supports him) and he has once again started attending public school. He has been making great progress this year! Through 2014, this was for just a partial day, but we're working now to extend that to a full day in in the early part of 2015.

Getting Into Microcontrollers

This year I got a Raspberry Pi (and later an Arduino). I'm loving playing with electronics again! I've been putting together a "GaragePi" project, which I've written about a couple of times now:

The Rise and Fall of My SonarQube Plugins

This one's a bit painful still, but in 2014 I published my SonarQube plugin to support ReSharper. About a month later, the cord was pulled and it died a quick death along with my plugin to support NUnit reports.

This was a fairly hurtful experience for me, as I poured a LOT of time and effort into putting these together and has turned me off from contributing to open source projects for a while. It did, however, allow me to turn my focus on That Conference.

All the other stuff

And, of course, there's been so much more. From my DIY projects at home, surviving the Polar Vortex, finally breaking 1000 on StackOverflow, to becoming the housemate of a cat. In 2014, I posted 16 entries to this blog and had approx 92,300+ pageviews -- and may actually make enough money on cumulative ad views for Google to actually cut me a check this year!

All-in-all, it's been a pretty good year! And 2015 is looking to continue the trend.



In my previous post, Garage Pi - My Raspberry Pi Playground, I described the Raspberry Pi-based system I had setup in my garage to detect when the garage door was left open and turn on an LED inside the house. Since then, I've expanded the system by:

  • replaced the red LED inside the house with an RGB LED
  • added a new custom board with a photo-resistor and temperature sensor

The photo-resistor can be used to determine relative light levels (its resistance value decreases as the amount of light hitting its surface increases). I will use this to determine if the light bulb inside the garage has been left on and turn on the green LED if so.

The temperature sensor is exactly what it sounds like -- it provides the current temp. Currently, I'm not doing anything with this value (aside from printing it to the console), but v3 of this project will include a database to store all of the various sensors' values over time, including the temperature, followed shortly by v4 which will add a web UI to the system.

In this post, I'll document what I did to hook-up the photo-resistor and the temperature sensor. If you haven't already, I suggest at least skimming through my original post.

Make sure to also check out my next post GaragePi v3: Data Persistence and Visualization and Other Small Improvements

Adding a Temperature Sensor

Since the Pi doesn't have any analog inputs, a TMP36 module (common for Arduino-based projects) will not work. Instead, I used a DS18B20 Thermometer I picked up on Amazon.com (2 for about $3 -- though you can get them cheaper on ebay).

The device has three pins:

  • Voltage (3.3V-5V)
  • Data
  • Ground

It uses the "1-wire" standard, meaning you can have multiple sensors and connect all of their data pins to the same input pin. Each device has a unique serial number, which is used to access the data. For this device, I won't read the pin directly. Instead, I'll load a couple of linux kernel modules that will read the device data and provide a virtual text file with the sensor's data.

I relied heavily on the AdaFruit DS18B20 tutitorial when putting together my hardware and software for this module.

The hardware is pretty straight-forward. Connect the voltage pin to 3.3V, the ground pin to ground and the data pin to one of your open GPIO ports. You'll also need to put a pull-up resistor between the data and voltage pins. I read that this needs to be between 4.7k ohms and 10k ohms, so I used a spare 56k ohm resistor I had.

I connected the data pin to GPIO pin 4, though it doesn't seem to matter which pin you use.

Here, you can see the sensor on my breadboard. It's a really small chip, so it's hard to make out with all the wires and the resistor.

Adding a Photo-Resistor

A photo-resistor, or photocell, is a resistor that changes its resistance when exposed to light. The more light, the less it's resistance. But since, unlike an Arduino, the Pi doesn't have an analog input where I can directly measure the resistance, I added a 1uF capacitor connected to ground that will basically suck up the voltage coming across the resistor until it fills the capacitor, at which point our GPIO pin will see a HIGH voltage signal. Then I time how long it takes fill the capacitor instead of measuring the resistance directly. The more resistance (ie: the less light), the longer it will take before I get a HIGH voltage signal.

The circuit is again pretty simple. I went to RadioShack and bought the only 1uF capacitor they had in stock, which was a 50V 1uF PET capacitor. It's important to note that these differ from your typical electrolytic capacitor in that PET's don't have polarity and can be connected in any direction. If you're using an electrolytic capacitor, make sure to connect it with the negative pin going to ground. My circuit diagram shows an electrolytic capacitor since I couldn't find a PET module in Fritzing.

So the circuit is this: connect one pin of the photocell to 3.3V, and the other connects to your GPIO pin and to your capacitor. The other leg of the capacitor connects to ground.

When we get to the software, you'll see that I have to first empty the capacitor each time I perform a reading by setting my input pin to LOW.

The Circuits

Here's the logical diagram for both the temperature and photo-resistor circuits:

Here's the completed breadboard:

And for those following along, here's the full circuit diagram for my GaragePi, including the Ultrasonic Range Finder module from my original post and the new RGB LED.

Going Off-Breadboard

Just like with my range finder circuit, I soldered these two circuits onto a PCB so that I could mount them in the garage. I used an old 4-wire cable that originally was intended to connect your computer's CD-ROM to your soundcard (I knew I kept all those old wires for something).

And here it is mounted in the garage along with the other components. I have the new temperature and light board mounted on the left side of the picture, facing the light bulb (just off camera) and as shaded as I could from the window (to reduce false-positive readings from sunlight). You can see the sonic range finder module in the back right and the Pi in the middle. The gray cable heading off to the left is the telephone cable running to the inside LED. The white wires go from the garage door opener to it's safety sensors and are unrelated to the GaragePi, aside from me hanging the LED wiring on it.

The Inside LED

One other change I made was to replace the red LED I had mounted inside with a Red-Green-Blue tri-color LED. This also me to illuminate a different color for different issues. Currently, I show red if the garage door is open and green if the light is left on. One issue I have currently is that the green LED is much brighter than the other two. I could fix this by adding a larger resistor to that LED's pin, but not today.

Since I had used cat3 2-line telephone cable for the run between the LED and the Pi, I had 4 wires already in place that I could use - I just had to remove the old LED and feed the new one's wires through the same path.

The Code

In addition to adding the light and temperature sensor logic, I made two other big changes to the code:

  1. I split the Controller classes into separate files and moved the init() logic into the constructors (__init__())
  2. I put the code up on GitHub

Since the code is now on GitHub, I won't post most of it here.

The PhotocellController class

Source code on GitHub

The logic for getting a reading starts with putting the pin into output mode and writing LOW to the pin. This has the effect of discharging the capacitor. Then, I put the pin back into input mode and time how long it stays in the LOW state. I also put in a timeout because, at least for the size of resistor I'm using, it takes a VERY long time to fill the capacitor at night when the light is off. When the light is on, I usually get readings in under 300, so there's a large margin there.

def resistanceTime(self):
    print("    Reading photocell")
    timeout = 3000
    reading = 0
    GPIO.setup(self.pin, GPIO.OUT)
    GPIO.output(self.pin, GPIO.LOW)
    time.sleep(0.1)

    GPIO.setup(self.pin, GPIO.IN)
    # This takes about 1 millisecond per loop cycle
    while (GPIO.input(self.pin) == GPIO.LOW):
        reading += 1
        if reading > timeout:
            break
    print("    Light Reading: {}".format(reading))
    return reading

The TempSensorController class

Source code on GitHub

Again, this code was based in large part on the AdaFruit walk through.

In the __init__(), I take a sensor index (in the event I ever add a 2nd or 3rd temp sensor), then I call modprobe to load the os modules needed to read these values. I then go find the virtual files that store the readings.

I've noticed a few times that the very first time I run the script after a reboot, the device files aren't loaded yet when I try to read them and an exception is thrown. Immediately re-running the script works fine. This is something I'll need to fix in the future.

def __init__(self, sensorIndex):
    self.sensorIndex = sensorIndex
    print("Initializing Temperature Sensor")

    os.system('modprobe w1-gpio')
    os.system('modprobe w1-therm')

    base_dir = '/sys/bus/w1/devices/'
    device_folder = glob.glob(base_dir + '28*')[self.sensorIndex]
    self.device_file = device_folder + '/w1_slave'

The other methods in this class, _readTempRaw() and readTemp() are reading the values out of the virtual file and parsing them. The temperature is stored as Celsius, so there's also logic to convert it to Fahrenheit and the return value is both temperature scales. I won't post the code here -- head to GitHub or the AdaFruit site if you want to see it.

Main loop

I did a lot of refactoring of the main loop and broke out the garage door check and the light check into separate functions.

For the light check, if the photocell's response time is faster than a constant value, then I consider the light to be on. To come up with the constant, I ran the tests over and over with the light and off to see what values came back. I learned two important things:

  • The longer the photocell is exposed to light, the less resistance it has -- even if the light remains constant. This means that when the light is initially turned on, the values coming back close to 300, but within about a minute, they are closer to 50.
  • I have a CFL bulb in the garage, which takes a while to warm up in the cold of winter -- sometimes up to 3 minutes before reaching full brightness. This means that when I first turn on the light after it's cooled, the light level may not be high enough to trigger my alert.

I also added an instance of the LED controller for the currently unused blue LED. This was needed to ensure the LEDs pin was set to LOW. Initially I didn't have this and I saw a very slight blue glow from the LED due to a very low voltage existing on the pin if it wasn't explicitly set to LOW.

So here's what the main class looks like now. Again, you can find the full source out at my GitHub repo for this project