<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Brett's Blog</title><link href="https://brettsbeta.com/blog/" rel="alternate"/><link href="https://brettsbeta.com/blog/feeds/all.atom.xml" rel="self"/><id>https://brettsbeta.com/blog/</id><updated>2020-08-27T16:38:46+00:00</updated><entry><title>CANbus data to JSON API</title><link href="https://brettsbeta.com/blog/2020/08/27/canbus-data-to-json-api/" rel="alternate"/><published>2020-08-27T16:38:46+00:00</published><updated>2020-08-27T16:38:46+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2020-08-27:/blog/2020/08/27/canbus-data-to-json-api/</id><summary type="html">&lt;p&gt;Particle has released the TrackerOne and TrackerSOM, a "a field-ready asset tracking solution" and reference design for a custom tracking solution. The board includes an integrated CAN Bus controller and transceiver able to connect to a car's OBD-II port.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Particle has released the TrackerOne and TrackerSOM, a "a field-ready asset tracking solution" and reference design for a custom tracking solution. The board includes an integrated CAN Bus controller and transceiver able to connect to a car's OBD-II port.&lt;/p&gt;
&lt;p&gt;A couple years ago I built the same thing from parts and an Electron. CAN/OBD-II data comes through the port encoded and the decoding library couldn't be run on the STM32F205RGT6 120MHz ARM Cortex M3, so I built a service that could be called via a Particle webhook. I've been sitting on it for ages, so now I'm making it public:&lt;a href="http://api.greencam.xyz/canbus"&gt;http://api.greencam.xyz/canbus&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The service can be used to translate a single CAN frame or in a batch mode. For single frames, simply include the HEX data in the query parameter:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://api.greencam.xyz/canbus/single_frame?can_frame=cf00300;d1000014ff0f5e7d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and receive a plaintext result. This method is effective for basic calls from a webhook or script.&lt;/p&gt;
&lt;p&gt;To batch process CANbus data, POST the CANbus data as a JSON object, either as a set of frames:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &amp;quot;can_frames&amp;quot;: [
    &amp;#39;cf00300;d1000014ff0f5e7d&amp;#39;,
    &amp;#39;18fedf00;7da0287d7dfffff0&amp;#39;,
    &amp;#39;18feef00;ffffff00067dfffa&amp;#39;,
    &amp;#39;18fee000;4624040042240400&amp;#39;
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;or as a stream separated by \n line endings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &amp;quot;can_stream&amp;quot;: &amp;quot;18fedf00;7da0287d7dfffff0\ncf00300;d1000014ff0f5e7d\n18feef00;ffffff00067dfffa\n18fee000;4624040042240400n&amp;quot;,
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to the URL: &lt;a href="http://api.greencam.xyz/canbus/json"&gt;http://api.greencam.xyz/canbus/json&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note: there is a limit of 1000 frames and 200 calls per day or 1 call per second on all of these services.&lt;/p&gt;
&lt;p&gt;Feel free to reach out if you'd like to know more, and if you are interested in a tailored tracking solution for more sophisticated applications or original products I am available for consulting or contract.&lt;/p&gt;</content></entry><entry><title>Flask on Dreamhost Shared Website Hosting</title><link href="https://brettsbeta.com/blog/2020/07/21/flask-on-dreamhost-shared-website-hosting/" rel="alternate"/><published>2020-07-21T15:29:47+00:00</published><updated>2020-07-21T15:29:47+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2020-07-21:/blog/2020/07/21/flask-on-dreamhost-shared-website-hosting/</id><summary type="html">&lt;p&gt;This tutorial will guide you through deploying Flask on a Dreamhost shared plan using Passenger. Dreamhost has first-party technical notes for nearly every step in this process, this walkthrough simply clarifies and connects them into a step-by-step path. Thank you to Matt Carrier's excellent tutorial from 2013 for getting me to 90%, this is essentially just an updated version.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This tutorial will guide you through deploying Flask on a Dreamhost shared plan using Passenger. Dreamhost has first-party technical notes for nearly every step in this process, this walkthrough simply clarifies and connects them into a step-by-step path. Thank you to Matt Carrier's excellent tutorial from 2013 for getting me to 90%, this is essentially just an updated version.&lt;/p&gt;
&lt;h1&gt;Initial Setup&lt;/h1&gt;
&lt;p&gt;First create a new domain or subdomain on your Dreamhost account – if you reuse a domain, this process will overwrite existing data. (If you don't own a domain you can register within Dreamhost but a service like Hover.com will make moving easier if/when you outgrow Dreamhost.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login to your Dreamhost admin panel&lt;/li&gt;
&lt;li&gt;Go to Domains -&amp;gt; Manage Domains in the left navmenu&lt;/li&gt;
&lt;li&gt;Click Add Hosting to a Domain / Sub-Domain button on the right.&lt;/li&gt;
&lt;li&gt;Fill in your Domain to host: with something fun like &lt;code&gt;newproject.mydomain.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Fill in the next options to your preference.&lt;/li&gt;
&lt;li&gt;Check the Passenger (Ruby/NodeJS/Python apps only) box. This is important.&lt;/li&gt;
&lt;li&gt;Click Fully host this domain.&lt;/li&gt;
&lt;li&gt;Wait a few minutes while this domain is created.&lt;/li&gt;
&lt;li&gt;Refresh the Manage Domains page so you can see the new domain.&lt;/li&gt;
&lt;li&gt;Under the SECURITY column will be a link &lt;code&gt;HTTPS Not Secure&lt;/code&gt; – click this.&lt;/li&gt;
&lt;li&gt;Click the Select this certificate button under the &lt;code&gt;LET'S ENCRYPT SSL CERTIFICATE&lt;/code&gt; card. Don't worry, it is free.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Install Python3&lt;/h2&gt;
&lt;p&gt;Python 2.x was deprecated January 1, 2020 after a 14 year transition period but Dreamhost still defaults to it for most users. It is also good practice to install a custom, private version to isolate and control your environment from the server's. The following procedure installs Python3 and changes your user path to default to it. This procedure closely follows Dreamhost's own support document.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSH into your domain: ssh username@mydomain.com&lt;/li&gt;
&lt;li&gt;Go to your newly created project's directory: cd newproject.mydomain.com&lt;/li&gt;
&lt;li&gt;Execute the following commands in order, one at a time. Some will take several minutes to complete. If a newer version Python3 exists, correct the links and names as appropriate:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;py3_tmp&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;py3_tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="n"&gt;tgz&lt;/span&gt;
&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zxvf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="n"&gt;tgz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="n"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;export PATH=$HOME/opt/python-3.8.2/bin:$PATH&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;bash_profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The last command will set the default Python for this user. To activate it, reload your profile: &lt;code&gt;. ~/.bash_profile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Now check your work: &lt;code&gt;which python3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;double check: &lt;code&gt;python3 --version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and triple-check:&lt;code&gt;pip3 --version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You may delete the &lt;code&gt;py3_tmp&lt;/code&gt; directory when you have verified everything.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Install Flask within a virtual environment&lt;/h2&gt;
&lt;p&gt;Using the private version of Python you've just installed, we now create a virtual environment and install Flask. A virtual environment ensures your code, libraries and extensions are isolated from the rest of the applications on the server. Dreamhost also has a KB article walking you through this. In brief:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure the latest pip is installed: &lt;code&gt;python3 -m pip install --upgrade pip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install virtualenv: &lt;code&gt;pip3 install virtualenv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check it: &lt;code&gt;which virtualenv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;which python3&lt;/code&gt; and use the output in the next step:&lt;/li&gt;
&lt;li&gt;Create the &lt;code&gt;venv&lt;/code&gt; itself, using the path from the previous step: &lt;code&gt;virtualenv -p /paste/path/from/above/python-3.8.2/bin/python3 venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Activate your venv: &lt;code&gt;source venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and check again: &lt;code&gt;python -V&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your command prompt should now be prepended with the name of the venv, indicating all installations will happen within the venv. Next, Flask itself is easy to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure pip is updated: &lt;code&gt;pip install --upgrade pip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install Flask:&lt;code&gt;pip install Flask&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install any other dependencies you will need, like requests, Flask-Login, Flask-Mail, Flask-SQLAlchemy, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configure Passenger&lt;/h2&gt;
&lt;p&gt;Passenger is an open source web and application server and gateway interface. Basically it is the connecting framework for web servers (which Dreamhost provides) to forward requests to web applications or frameworks written in Python (which you will write). Setup is a breeze:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a Passenger configuration file: &lt;code&gt;nano passenger_wsgi.py&lt;/code&gt; (feel free to use vi or emacs or your preference. Don't email me.)&lt;/li&gt;
&lt;li&gt;Enter the following into &lt;code&gt;passenger_wsgi.py&lt;/code&gt;, making sure you replace 'yourdomain.com' with your information:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;INTERP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HOME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;yourdomain.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;venv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hello from Passenger&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Save and close this file.&lt;/li&gt;
&lt;li&gt;Make the file executable: &lt;code&gt;chmod +x passenger_wsgi.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a restart button: &lt;code&gt;mkdir tmp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Restart: &lt;code&gt;touch tmp/restart.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, everything should be running. Go check it: &lt;code&gt;https://www.&amp;lt;yourdomain&amp;gt;.com&lt;/code&gt; will show the text, 'Hello from Passenger'. If not, double-check everything above.&lt;/p&gt;
&lt;h2&gt;Create your Flask App&lt;/h2&gt;
&lt;p&gt;Now we're going to make a real Flask application. (You will likely want start using your favorite IDE and upload the files to the server when finished rather than editing directly on the server. You will also likely want a local environment and to start a GIT repository. These steps are outside the scope of this tutorial.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a directory: &lt;code&gt;mkdir app&lt;/code&gt; if you're still in the server.&lt;/li&gt;
&lt;li&gt;Create a new file within &lt;code&gt;app/&lt;/code&gt;called &lt;code&gt;__init__.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enter the following and save:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Create the file routes.py within app/Enter the following and save:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello from Flask!&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;passenger_wsgi.py&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;INTERP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HOME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;yourdomain.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;venv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The project structure should look like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mydomain.com/
----venv/
----app/
--------init.py
--------routes.py
----passenger_wsgi.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Restart Flask: touch tmp/restart.txtAnd now go check your application again – you should see 'Hello from Flask!'&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Build&lt;/h2&gt;
&lt;p&gt;You now have a fully functional, if trivial, Flask app running on Dreamhost Shared webhosting. You can make some amazing software with SQL backends and complex frontends from here – I suggest Miguel Grinberg's Flask Mega-Tutorial for the basics, or for data science applications, Data Visualization with Python and JavaScript is an excellent resource.&lt;/p&gt;</content></entry><entry><title>Just One State - immigration lies</title><link href="https://brettsbeta.com/blog/2016/11/20/just-one-state---immigration-lies/" rel="alternate"/><published>2016-11-20T09:34:23+00:00</published><updated>2016-11-20T09:34:23+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2016-11-20:/blog/2016/11/20/just-one-state---immigration-lies/</id><summary type="html">&lt;p&gt;My grandmother recently forwarded an email that has been going around the internet. Snopes is my usual source of fact-checking but it, along with about a half dozen other debunking sites, didn't do this particular chain justice, despite this chain having been around for ~15 years. This was the best of the bunch, which makes sense as the LA Times was, inaccurately, listed as the source of the statistics. So I checked every statement myself. Below is my letter to my grandmother:&lt;/p&gt;</summary><content type="html">&lt;p&gt;My grandmother recently forwarded an email that has been going around the internet. Snopes is my usual source of fact-checking but it, along with about a half dozen other debunking sites, didn't do this particular chain justice, despite this chain having been around for ~15 years. This was the best of the bunch, which makes sense as the LA Times was, inaccurately, listed as the source of the statistics. So I checked every statement myself. Below is my letter to my grandmother:&lt;/p&gt;
&lt;p&gt;So this email chain is full of incorrect statements. As my sisters know too well, whenever one of these comes to me I will fact-check it. I do not believe anything I've written below is partisan, I'm not saying we should be voting Red or Blue or Purple: these are simple numbers, mostly from the Census and other sources, and inarguable math. I have cited every source used. If there are inaccuracies, please send them back to me corrected and attributed. I believe in facts, in logic and in intelligence and the basis for exercising that intelligence is a correct foundation. Hence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;40% of all workers in LA County (10.2 million people) are working for cash; and not paying taxes. This is because they are predominantly illegal immigrants, working without a green card.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The 40% statistic appears to come from a 2007 (near the end of the Bush administration) report. By October 2015, the analysis of US Census ACS PUMS (American Community Survey 2009-2013 Public Use Microdata Sample) and LODES data along with LA specific IMPLAN data indicates informally employed workers has been reduced to approximately 20 percent of the workforce and that 65 percent of the city’s informal workers are unauthorized immigrants, resulting in approximately 13% of LA workers being illegal immigrants. Many of these are concentrated in low-wage jobs: A third of the low-wage workers within low-wage industries are estimated to be unauthorized immigrants, making this the vast majority of the jobs taken.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;95% of warrants for murder in Los Angeles are for illegal aliens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This number is hard to verify because Los Angeles County District Attorney's Office does not track it. This figure appears (unsourced) in Heather MacDonald's 2005 testimony before the House Judiciary Subcommittee on Immigration, Border Security, and Claims and refers specifically to outstanding homicide warrants. One University of California Davis study concludes that nearly half of the LAPD's outstanding arrest warrants involved Mexican nationals who were presumed to have fled the country and that most are many years, even decades old because the United States does not have extradition treaties with most Latin American countries so the alleged murderers flee and the case can never be closed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;75% of people on the most wanted list in Los Angeles are illegal aliens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This list is online but the LAPD makes no claim to race, ethnicity or immigration status anywhere.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Over 2/3 of all births in Los Angeles County are to illegal alien Mexicans on Medi-Cal, whose births were paid for by taxpayers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In 2013, 46.1% of births were paid for by MediCal , but undocumented immigrants are actually ineligible for Medi-Cal. CA state, not using federal funds, pays for coverage under a PRUCOL eligibility status. Some sources indicate about half the births in Los Angeles County were to undocumented women, certainly 56.4% were to Hispanic women. Neither hospital nor state records include immigration status at time of birth, nor do they ask&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nearly 35% of all inmates in California detention centers are Mexican nationals; they are here illegally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The US DOJ says CA had 16,089 non-citizen inmates and 147,578 total inmates in 2011, so around 10%. This number includes all non-citizens, not only those of Mexican descent, and includes those held by held by Immigrations and Customs Enforcement in detention centers where illegal immigrants are detained before deportation. See also&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Over 300,000 illegal aliens in Los Angeles County are living in garages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The city of LA issued citations against the owners of 2,560 illegal units between 2010 and 2015. UCLA estimates that at least “20% of existing lots challenge the rigidity of the R1 code by converting garages or illegally erected shacks into residential spaces” in one Latino dominant neighborhood. Overall however, as seen on this year's ballot, this isn't a problem of illegal aliens but a problem of LA's housing situation in general.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The FBI reports half of all gang members in Los Angeles are most likely illegal aliens from south of the border.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The FBI has never stated any such statistic and has specifically denied this one: 2011 National Gang assessment. This appears to originate from an op-ed by attorney Carol Platt Liebau — “Of the membership of the notorious 18th Street gang, estimated at 20,000, fully 60% are illegal aliens, according to a 1995 report by the state Department of Justice.” This particular gang, the 18th Street, is a transnational criminal gang from South America and is by definition heavily non-American. Saying this is akin to saying the KKK is full of racist white people: it is by definition true. The other large and prominent gangs in LA — including the Crips, Bloods and of course Aryan Nation — are heavily non-immigrant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nearly 60% of all occupants of HUD properties are illegal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Housing and Community Development department of LA estimates that as many as 5 percent of HUD housing units are occupied by illegal aliens. Similarly, FAIR says “We have found no studies that document the occupancy of public housing by illegal aliens and their U.S. citizen family members. We estimate that there will be as many as 3 million low-income families with one or more illegal alien parent and U.S.-born children — with the latter making the family eligible for public housing. That would amount to roughly three percent of all families in public housing.”&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;21 radio stations in LA are Spanish- speaking.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A little high according to http://www.ontheradio.net/metro/Los_Angeles_CA.aspx but I have to say, so what? We live in a free capitalist society, these radio station owners have clearly found a profitable enterprise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In LA County, 5.1 million people speak English; 3.9 million, speak Spanish.
(There are 10.2 million people, in LA County.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The latest numbers as of 2010 based on the ACS are: 3,966,317 English speakers, 3,582,992 Spanish speakers. https://apps.mla.org/map_data_results?state_id=6&amp;amp;county_id=37&amp;amp;mode=geographic&amp;amp;zip=&amp;amp;place_id=&amp;amp;cty_id=&amp;amp;ll=all&amp;amp;a=&amp;amp;ea=&amp;amp;order=r&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Less than 2% of illegal aliens are picking our crops but 29% are on welfare. See… http://www.cis.org/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;According to the 2015 NAWS roughly 1.2 million to 1.75 million farmworkers are undocumented and roughly 750,000 to 1.3 million farmworkers are United States citizens or lawful immigrants. If this 2% number is true, then simple math results in 60,000,000 illegal immigrants in the US. Clearly it is much higher.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Over 70% of the United States ' annual population growth, (and over 90% of California , Florida , and New York ), results from immigration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Setting aside the argument of why this is a good or bad thing, this one was easy: http://www.census.gov/popest/data/national/totals/2015/index.html and https://www.dhs.gov/immigration-statistics/yearbook/2014/table1
Dec 1, 2014 the estimated US population was 320,086,785, while Jan 1, 2011 the population was 310,632,519. We've grown by 16,073,456. Legal immigration accounts for 4,100,742. Simple math says that this is 43%.&lt;/p&gt;
&lt;p&gt;Illegal immigration is harder to estimate because number of immigrants that cross the border illegally each year are not directly countable. The DHS made 2,547,669 'removals or returns' during this time period and the DHS estimates a rate of 33%. Again using simple math, 674,000 persons per year probably get into the US, bringing the total up to 50.1% of population grown accounted for my immigration, legal and illegal.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Also, 29% of inmates in federal prisons are illegal aliens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In fact, this one has gone up: 36.7 percent of federal sentences in FY 2014 were of illegal immigrants. However, immigration cases themselves are the second most common federal case after drugs, accounting for 29.3% of the total federal caseload and 66% of non-citizen offenders committed an immigration offense. So this statistic is in fact saying that the US is effective at arresting illegal immigrants for being illegal immigrants. For violent or drug-related crimes, illegal immigrants account for less than 12% of federal inmates.&lt;/p&gt;</content></entry><entry><title>3D printed Ixe Axe sheath</title><link href="https://brettsbeta.com/blog/2016/08/21/3d-printed-ixe-axe-sheath/" rel="alternate"/><published>2016-08-21T10:41:53+00:00</published><updated>2016-08-21T10:41:53+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2016-08-21:/blog/2016/08/21/3d-printed-ixe-axe-sheath/</id><summary type="html">&lt;p&gt;A recent trip to Alaska made me realize that an unprotected ice axe leaves holes in everything it touches. So I whipped up these 3D printable axe protectors!&lt;/p&gt;</summary><content type="html">&lt;p&gt;A recent trip to Alaska made me realize that an unprotected ice axe leaves holes in everything it touches. So I whipped up these 3D printable axe protectors!&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="media/20160821104153/DSC_5850-1.jpg"&gt;
&lt;img alt="" src="media/20160821104153/DSC_5843.jpg"&gt;
&lt;img alt="" src="media/20160821104153/DSC_5847.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Download the models here&lt;/p&gt;
&lt;p&gt;Designed to fit a Petzl Quark, it is easy to fine-tune to different axes. Using ABS, the pick protector is 3.9g and the adze is 11.3g. Friction mostly works but the 3mm holes are there to secure it with chord. Personally, I this this is better than the OEM caps... Certainly better than the cardboard, duct tape, plastic tubing, garden hose, tennis balls, pool noodle, bike tire, or any other method I've tried.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="media/20160821104153/DSC_5854.jpg"&gt;&lt;/p&gt;</content></entry><entry><title>Chicago Bus App</title><link href="https://brettsbeta.com/blog/2015/08/27/chicago-bus-app/" rel="alternate"/><published>2015-08-27T12:00:42+00:00</published><updated>2015-08-27T12:00:42+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-08-27:/blog/2015/08/27/chicago-bus-app/</id><summary type="html">&lt;p&gt;Chicago is a Big Data city.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Chicago is a Big Data city.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://data.cityofchicago.org"&gt;https://data.cityofchicago.org&lt;/a&gt; serves 914 distinct, publicly accessible data sets including Beach Water Quality, Rat Infestations, Employee names and salaries. Chicago is "dedicated to promoting access to government data and encouraging the development of creative tools to engage and serve Chicago's diverse community". They have a Github site, video tutorials and sample API code. It is an immense data store!&lt;/p&gt;
&lt;p&gt;It is a terrific strategy. For example: building a city bus smartphone app. Design, support, customer service, bug fixes, inaccuracies – all cost time and money. Chicago has chosen to make data they already collect as part of operations publicly available. They've equipped independent developer citizens with the data necessary to build the best bus application they can, in a competitive marketplace, resulting in a dozen bus apps at zero cost to the city in time, money or other resources. Not to mention a community of invested citizens. Brilliant.&lt;/p&gt;
&lt;p&gt;Then again, maybe not. The marketplace is competitive but the rewards are minimal, driven by the rush-to-the-bottom dynamic of the smartphone app ecosystem. The available apps are flexible, feature-filled, and some (very few) are even aesthetically well-designed and stable. They're also all terrible. Every app required 3-6 clicks and 30+ seconds of interaction to get the bus information I want, even though that request never changes. I need to go to work and back every day, at the same time, from the same place, on the same route. I don't need to know anything else 95% of the time.&lt;/p&gt;
&lt;p&gt;Which brings us to the second beauty of a public API: I can personally access it with minimal hassle. A quick web page, with the apple-mobile-web-app-capable set to yes, and an app designed for me, by me is only a couple hours away. A web app requires no developer agreements, sidesteps the App Store, can be updated in real time, and can be private.&lt;/p&gt;
&lt;p&gt;So here's my "app": it displays the time until the next 5 buses on my route arrive at my stop. You can't change the routes, you can't change the stops. There is zero flexibility and there is one feature: I open the app and it displays the minutes until the bus leaves. It refreshes every 30 seconds or when I click anywhere. All it required is a very, very simple PHP script (if you try with Javascript you will run into the cross-domain request limitation) which dynamically renders a Bootstrap based web page.&lt;/p&gt;
&lt;p&gt;First we grab the XML Bus feeds from the API. Get your own key to follow along:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;according&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;timeNowURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://www.ctabustracker.com/bustime/api/v1/gettime?key=MYKEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;timenowxml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simplexml_load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;timeNowURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;feed not loading&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;busCurrentTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;timenowxml&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IDs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;nextBus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://www.ctabustracker.com/bustime/api/v1/getpredictions?key=MYKEY&amp;amp;stpid=STPID,STPID2&amp;amp;rt=RTID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;nextbusxml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simplexml_load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;nextBus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;feed not loading&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;buses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;busxml&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I grab the bus system's current time because it can differ from the actual time by over a minute. I also grab the two routes I'm interested in. PHP's &lt;code&gt;simplexmp_load_file&lt;/code&gt; takes care of calling the request and parsing the result. &lt;code&gt;strtotime()&lt;/code&gt; works wonders on every date string I've ever thrown at it (much, much better than the javascript version, which required parsing character by character to get a Date object).&lt;/p&gt;
&lt;p&gt;I then prepare the recieved data for display:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;busses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;rtdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Westbound&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;//separate into east and westbound data&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predictedTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;prdtm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;//get the predicted time&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;minutesUntil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predictedTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;busCurrentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;//calculate minutes until and round up (to not provide false data and give a small cushion)&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;westCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// this counter is used for simplification purposes.&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predWest&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;westCounter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;minutesUntil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;//add the prediction to an array.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;rtdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Eastbound&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;//repeat for eastbound&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predictedTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;prdtm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;minutesUntil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predictedTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;busCurrentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;eastCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;predEast&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;eastCounter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;minutesUntil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is more data available, including approximate feet to the next stop, but it isn't necessary.
To render the HTML I brute force a few calls like this inside my HTML template:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;if ($westCounter &amp;gt;= 1) { echo $predWest[1]; } else { echo"-"; }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;"if there is a value then use it, or display a placeholder."&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# screenshot of app #&amp;gt;" src="media/20150827120042/IMG_0307-e1436396990534.png" title="IMG_0307-e1436396990534.png"&gt;&lt;/p&gt;
&lt;p&gt;Maybe in the future I could add features like user configurable routes or stops, but I don't need that. Anyone can customize those in the code and then leave it static. I still have a Chicago Bus app on my phone for those rare times when I need a different route. Google is pretty good too.&lt;/p&gt;
&lt;p&gt;But in the mornings, I open this app with one touch and let it sit on the counter, refreshing every 30 seconds, until I'm ready to leave – at which point I can see, with zero interaction, how many minutes until the next bus.&lt;/p&gt;</content></entry><entry><title>0.6% of my Income</title><link href="https://brettsbeta.com/blog/2015/03/29/06-of-my-income/" rel="alternate"/><published>2015-03-29T17:00:28+00:00</published><updated>2015-03-29T17:00:28+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-03-29:/blog/2015/03/29/06-of-my-income/</id><summary type="html">&lt;p&gt;Something tickled my pattern matching circuits while reading the countless articles analyzing the pricing of the Apple Watch and Apple's strategy behind it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Something tickled my pattern matching circuits while reading the countless articles analyzing the pricing of the Apple Watch and Apple's strategy behind it.&lt;/p&gt;
&lt;p&gt;Of the 38ish distinct Apple Watch models there are, to me, only five major style variations (ignoring size): the basic aluminum Sport, at $399, less or more depending on sizes and bands. The $699 steel Watch with classic, leather or modern bands. Upgrade to a steel link band for $999 ($1099 in black). Then there is the gold Edition starting at $10,000 and going all the way to $17,000.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$399&lt;/td&gt;
&lt;td&gt;42mm aluminum Sport&lt;/td&gt;
&lt;td&gt;Sport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$699&lt;/td&gt;
&lt;td&gt;42mm steel classic&lt;/td&gt;
&lt;td&gt;Watch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$999&lt;/td&gt;
&lt;td&gt;42mm steel link&lt;/td&gt;
&lt;td&gt;Watch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;td&gt;38mm gold sport&lt;/td&gt;
&lt;td&gt;Edition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$17,000&lt;/td&gt;
&lt;td&gt;38mm gold modern&lt;/td&gt;
&lt;td&gt;Edition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;According to the Tax Foundation's summary of IRS Federal Individual Income Tax data from 2011, US taxpayer individual income brackets break down like so:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bracket&lt;/th&gt;
&lt;th&gt;Income&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top 25%&lt;/td&gt;
&lt;td&gt;&amp;gt;$70,492&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top 10%&lt;/td&gt;
&lt;td&gt;&amp;gt;$120,136&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top 5%&lt;/td&gt;
&lt;td&gt;&amp;gt;$167,728&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top 1%&lt;/td&gt;
&lt;td&gt;&amp;gt;$388,905&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top 0.10%&lt;/td&gt;
&lt;td&gt;&amp;gt;$1,717,675&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;(After a few homebrews) I see a resemblance. Quantifying that resemblance I found surprising consistency: multiply each Apple Watch price by 170 and you get something cool:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;x170&lt;/th&gt;
&lt;th&gt;Income&lt;/th&gt;
&lt;th&gt;Bracket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$399&lt;/td&gt;
&lt;td&gt;42mm aluminum Sport&lt;/td&gt;
&lt;td&gt;$67,830&lt;/td&gt;
&lt;td&gt;$70,492&lt;/td&gt;
&lt;td&gt;Top 25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$699&lt;/td&gt;
&lt;td&gt;42mm steel classic Watch&lt;/td&gt;
&lt;td&gt;$118,830&lt;/td&gt;
&lt;td&gt;$120,136&lt;/td&gt;
&lt;td&gt;Top 10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$999&lt;/td&gt;
&lt;td&gt;42mm steel link Watch&lt;/td&gt;
&lt;td&gt;$169,830&lt;/td&gt;
&lt;td&gt;$167,728&lt;/td&gt;
&lt;td&gt;Top 5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;td&gt;38mm gold sport Edition&lt;/td&gt;
&lt;td&gt;$1,700,000&lt;/td&gt;
&lt;td&gt;$1,717,675&lt;/td&gt;
&lt;td&gt;Top 0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$17,000&lt;/td&gt;
&lt;td&gt;38mm gold modern Edition&lt;/td&gt;
&lt;td&gt;$2,890,000&lt;/td&gt;
&lt;td&gt;$2,800,000&lt;/td&gt;
&lt;td&gt;Tim Cook's 2013 Salary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Apple Watch sales estimates have ranged from 8 million to 41 million. There were 34,146,428 Americans who made more than $70,492 in 2013. Are people in this bracket the intended market? If so, at the very least it seems there are at least 35 million fiscally-possible American buyers. World wide, there are many more.&lt;/p&gt;
&lt;p&gt;The other thought that occurs to me: people claim purchasing a $10,000 watch is inconceivably irresponsible and ostentatious. Well, if your salary is $70,000 and you purchase an Apple Watch Sport you are being as irresponsible in your purchase, whether it lasts for 2 years or 50, as someone in the American 0.1% who purchases the Edition. Some estimate that 31% of Americans would like to purchase an Apple Watch – that seems irresponsible for at least some of them.&lt;/p&gt;
&lt;p&gt;Is the Apple Watch a metaphor for American income inequality? Or maybe an Illuminati price fixing conspiracy? Probably neither. Finding patterns in numbers is fun but generally meaningless. Homework assignment: Match the 2011 income brackets to the Golden Ratio Phi.&lt;/p&gt;</content></entry><entry><title>Solar Panel Analysis</title><link href="https://brettsbeta.com/blog/2015/02/12/solar-panel-analysis/" rel="alternate"/><published>2015-02-12T14:00:46+00:00</published><updated>2015-02-12T14:00:46+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-02-12:/blog/2015/02/12/solar-panel-analysis/</id><summary type="html">&lt;p&gt;I like solar power. I've purchased over a dozen consumer solar panels from Brunton to PowerMonkey to Goal Zero in a search of a favorite. Goal Zero's older Nomad7 (purchased at a nice discount from TheClymb) is the newest leader.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I like solar power. I've purchased over a dozen consumer solar panels from Brunton to PowerMonkey to Goal Zero in a search of a favorite. Goal Zero's older Nomad7 (purchased at a nice discount from TheClymb) is the newest leader.&lt;/p&gt;
&lt;p&gt;But this isn't a review of the Goal Zero; others have written excellent solar panel reviews, notably The Wirecutter and OutdoorGearLab. What I want to know is how to get the best performance, how to use it most effectively, to characterize it. What conditions produce best charge? What charge is produced under average conditions?&lt;/p&gt;
&lt;h2&gt;Technology Background&lt;/h2&gt;
&lt;p&gt;Goal Zero's Nomad7 uses Mono-Crystalline panel technology – the most efficient technology on the consumer market, up to 22%. They are reliable, have a very long life span of up to 30 years, generate the highest wattage per square foot and perform the best in high temperatures.&lt;/p&gt;
&lt;p&gt;The problem with crystalline panels is that they work best with direct, strong sunlight. In dispersed or ambient light the performance drop-off is severe – a slightly overcast day or shadowing, even of 10% of the panel, will drop their output to 20% of normal. My awesome time in Death Valley notwithstanding, direct, bright sunlight, like dry clothes or cold beer, is a luxury I'm often missing. So exactly how much efficiency is lost, and how cloudy can the day be?&lt;/p&gt;
&lt;h2&gt;Experiment Setup&lt;/h2&gt;
&lt;p&gt;Note: I'm using the older Nomad7. The newer Nomad 7 v2 has a few better specifications and improved design.&lt;/p&gt;
&lt;p&gt;The Goal Zero Nomad7 was placed in a south-facing window at an orientation with excellent sun exposure from dawn to dusk. Using the panel behind glass undoubtedly reduces the power generation but is a common setup for me whether indoors, sitting on the dash of my truck, or in a hostel, so I feel it is a valid test. Using a prototype model of Petiole Lab's SproutCore I created a simple experiment to record light intensity and voltage. I hooked up the Nomad7's USB output to the SproutCore's voltmeter while the SproutCore's LDR sensor (light dependent resistor, also known as a photo-resistor) measured light intensity throughout the day. Another caveat: I am recording the USB which is capped at 5V out. The raw output will produce up to 9V but can't be used to charge USB devices so I chose to measure USB.&lt;/p&gt;
&lt;p&gt;The Goal Zero Nomad7 was placed in a south-facing window at an orientation with excellent sun exposure from dawn to dusk.&lt;/p&gt;
&lt;p&gt;The LDR doesn't output a quantitative LUX reading but is good enough to give a rough indication of light intensity. A measured light intensity of 25% is roughly equivalent to dawn or dusk light: Enough that you wouldn't need a flashlight to get your gear together but you might need a tripod or wide aperture for sharp photos. An overcast but bright day might get you 50% to 75% light intensity, and a sunny day will be 80% to 95%.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;On a "typical day" (1/16/2015) with very little cloud cover and 11.6 hours of "legal" light (sunrise was 7:13 AM, and sunset was 4:50 PM) I recorded 9.34 hours of sunlight where the light intensity was greater than 25%.&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# alt text #&amp;gt;" src="media/20150212140046/20150116solarMon.png" title="20150116solarMon.png"&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, the panel doesn't output a full 5V charge without very full light, at around 90% measured light intensity. On this day those conditions only existed for 2.3 hours. Devices such as an iPhone will only charge when fed a clean and constant 5V. Goal Zero is very upfront about this: "If clouds pass by or weather changes, the charging can become interrupted. Phones will often reject the charge in cases like these." The problem goes deeper because, with the picky iPhone in particular, once a 'weird' voltage is detected the device may reject all power, even good 5.0V power, until it is unplugged and plugged in again. So if a cloud passes or a person stands over the panel and the output momentarily drops below 5V, your iPhone will stop charging until you attend to it. This is a very good reason to use a purpose-designed solar battery pack – such as Goal Zero's Switch 8 – to collect charge before plugging in your device.&lt;/p&gt;
&lt;p&gt;Goal Zero also states that "Goal Zero Rechargers/Power Pack charge well with solar regardless of the conditions – they never reject a charge opportunity." If the Goal Zero batteries like the Switch8 can charge at 4v, which only requires about 85% light intensity, then almost 5 hours of charge time was provided. The difference between 90% and 85% light intensity sounds small but is about the equivalent of a thin cloud passing in front of the sun or standing in a plane's shadow.&lt;/p&gt;
&lt;p&gt;When light intensity drops below 80% the voltage drops to 1 - 1.5V, probably not enough power to charge anything. I would like to know what the minimum charge voltage for the Switch8 is, but I'd bet that it is above 1.5V. It is likely to be around 3.7V unless they have clever energy harvesting circuitry.&lt;/p&gt;
&lt;h3&gt;Other days&lt;/h3&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# January 16 #&amp;gt;" src="media/20150212140046/20150118solarMon1.png" title="20150118solarMon1.png"&gt;&lt;/p&gt;
&lt;p&gt;The analysis above is for a good, sunny day. A couple days later, on January 18, the day was overcast and I got nearly zero charge out of the panel. It wasn't cloudy or rainy, just hazy. This was disappointing but is simply a characteristic of Mono-Crystalline panels technology. This result clearly illustrates that bringing this panel on a camping trip in the Pacific northwest may be pointless and relying on it is ill-advised.&lt;/p&gt;
&lt;p&gt;On January 15 and 17, results were fairly similar to sunny January 16. Passing clouds and light haze cause charging to momentarily cease but overall times of active power generation and average light intensities are very similar. The data can be seen below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# January 15 #&amp;gt;" src="media/20150212140046/20150115solarMon1.png" title="20150115solarMon1.png"&gt;
&lt;img alt="&amp;lt;# January 17 #&amp;gt;" src="media/20150212140046/20150117solarMon1.png" title="20150117solarMon1.png"&gt;&lt;/p&gt;
&lt;h2&gt;Interpretations&lt;/h2&gt;
&lt;p&gt;Broadly estimating based on the charts above, a nice (winter) day will only provide about 2 hours of "direct sunlight" and indirect but useful light for another 3 hours. Let's say my panel nets me 2W (4v at 0.5A) on average and is active (generating greater than 4v) for 5 hours of the day. Will my phone charge? Yes.&lt;/p&gt;
&lt;p&gt;My iPhone 5 battery has a nominal capacity of 5.45 Wh (1,440 mAh at 3.8V). Charging introduces many inefficiencies so using a Watts Up? Pro Electrical Meter, oPower analyzed actual charging to determine that the iPhone 5 consumes 9.5 watt-hours when charged from a wallwort. The inefficiencies of first charging a battery then charging a cell phone from that battery are probably worse but I can't measure those at this time so I'll just round to 10W.&lt;/p&gt;
&lt;p&gt;On this "average" day the Nomad7 should be able to charge 10W in about 5 hours of medium-good sunlight. In the real world this light will be interrupted so it will take longer. But the answer is yes: with a 7W panel I should be able to get 1 full iPhone5 charge in one day with some margin. On a great sunny summer day with more hours of more intense sunlight the Nomad7 should be able to charge a battery with twice the capacity of the iPhone5, though I will have to wait for summer to prove it.&lt;/p&gt;
&lt;p&gt;Does this match Goal Zero's advertisements? Yes. They claim a Nomad7 will charge a Switch 8 (8Wh, 2200mAh) in 4 – 8 hours. If we assume similar inefficiencies, we'll need 12.6Wh to charge the 8Wh Switch8, which under the conditions above will take about 6 hours of solid sunlight. That sounds like 4–8 hours. I'm impressed with Goal Zero's marketing: these numbers appear to hint that Goal Zero would like their products to work as advertised or better rather than drive sales by pushing lab specifications that won't occur in real usage.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What does all this boil down to? The manufacturer specifications are correct: the panel requires full, bright sun exposure and the use of a battery to collect charge before charging your devices. I shouldn't hope to get much charge in cloudy, hazy weather or under partial shadow. If I'm headed to the California Redwoods, the Pacific Northwest and the Appalachians in winter I'll probably stick with a large battery. Death Valley, Zion, or Red Rock Gorge? The Nomad7 should provide sustainable power for as long as I need to camp.&lt;/p&gt;
&lt;p&gt;I guess I need to go buy a Switch 8, or maybe the new Switch10.&lt;/p&gt;
&lt;p&gt;I also have a few more experiments I'd like to run. I want to try the same setup in the summertime with more light and more intensity. I'd also like to measure the amperage produced in addition to the voltage so I don't have to guess at total power generated (although this is an informed guess based on typical IV curves of solar panels). This will have to wait for a new current measurement sensor to be added to the SproutCore.&lt;/p&gt;
&lt;p&gt;Finally I want to compare Crystalline Solar Panel to PowerFilm – PowerFilm panels are less efficient but theoretically work better in partial light conditions. An initial informal test confirms this: in overcast weather the Nomad7 produced zero charge while the PowerFilm generated between 2 and 5V, tracking light conditions closely without the 80% drop-off the mono crystal panels exhibit. Hopefully I can run this experiment in a few weeks.&lt;/p&gt;
&lt;h2&gt;Addendum&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Battery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 4S&lt;/td&gt;
&lt;td&gt;5.3Wh (1432mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 5&lt;/td&gt;
&lt;td&gt;5.45Wh (1440mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 6&lt;/td&gt;
&lt;td&gt;6.91Wh (1810mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung Galaxy S III&lt;/td&gt;
&lt;td&gt;7.98Wh (2100mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung Galaxy S5&lt;/td&gt;
&lt;td&gt;10.6Wh (2800 mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 6plus&lt;/td&gt;
&lt;td&gt;11.1Wh (2900mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung Galaxy Note 4&lt;/td&gt;
&lt;td&gt;12.2Wh (3,220mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motorola Nexus 6&lt;/td&gt;
&lt;td&gt;12.2Wh (3220mAh)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you use a smartphone in the backcountry make it an iPhone and make it a small one. As seen in the table to the right, iPhones require about half the power of comparable phones for a similar functional life. A small iPhone will charge with fewer hours of sunlight when compared to any similar Android phone and an external battery pack will hold twice the number of charges. (I've seen "Power Saving" and "Ultra Power Saving" modes in Android phones but these don't allow use of the camera or GPS, which are the reasons I bring a smartphone into the backcountry). Bottom line, if you assume you can rely on about 8 - 12Wh of charge per day then any phone with a battery capacity over 8Wh will slowly discharge over your trip. You may require a larger panel, additional panels or may have to leave your phone off more often.&lt;/p&gt;
&lt;p&gt;Final tip: If you are using an iPhone, turn on Airplane mode before plugging into your external battery pack or solar panel. It will charge significantly faster and more efficiently – inactivating the GPS circuits, the cellular, bluetooth or WiFi antennas while simultaneously charging appears to allow more energy to go directly into the battery. The proof of this method will come in a later post. This may also work with Android phones, I have not tried it.&lt;/p&gt;</content></entry><entry><title>California Bay Area Beer Tckr</title><link href="https://brettsbeta.com/blog/2015/02/05/california-bay-area-beer-tckr/" rel="alternate"/><published>2015-02-05T17:56:06+00:00</published><updated>2015-02-05T17:56:06+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-02-05:/blog/2015/02/05/california-bay-area-beer-tckr/</id><summary type="html">&lt;p&gt;Beer ticking is simply the act of keeping a record of every different beer you drink. It encourages you to order new beers and try a greater variety. Serious tickers take pride in the number of ticks they have – and can be obsessive about it. Watch the Beertickers Documentary for a hilarious take on the hobby.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Beer ticking is simply the act of keeping a record of every different beer you drink. It encourages you to order new beers and try a greater variety. Serious tickers take pride in the number of ticks they have – and can be obsessive about it. Watch the Beertickers Documentary for a hilarious take on the hobby.&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# app screenshot #&amp;gt;" src="media/20150205175606/tckrScreenshot.png" title="tckrScreenshot.png"&gt;
The Bay Area list was originally compiled by Mario from Brewed For Thought. It attempts to include all bottled beers available in the nine counties of the California Bay Area (if rare draft-only beers were included this list would never end. Which is what makes the Bay Area such a great place to drink beer).&lt;/p&gt;
&lt;p&gt;I've kept this iteration of the Ticker List simple: it saves your ticks on the device you are currently using to view them. You cannot export, import, nor save across devices. You don't need an account and I don't need/want your email. It is written entirely in Javascript and HTML. Use something like Untappd for more complete, full-featured ticking. This list is just meant as a way to track the answer to "Have I drunk everything there is to drink in the Bay Area?", something Untappd can't answer for me.&lt;/p&gt;
&lt;p&gt;I've thought about making this more feature-filled, turning it into a native app, morphing it into a climbers ticker list. Would that be fun? Let me know!&lt;/p&gt;</content></entry><entry><title>Kilimanjaro</title><link href="https://brettsbeta.com/blog/2015/01/29/kilimanjaro/" rel="alternate"/><published>2015-01-29T00:00:13+00:00</published><updated>2015-01-29T00:00:13+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-01-29:/blog/2015/01/29/kilimanjaro/</id><summary type="html">&lt;p&gt;No moon, no stars, only blackness. Freezing, failing headlamps reveal only a couple of meters of terrain around me. The 60kmph wind howls, cutting off speech and chilling us through to the core; it supports my weight when I lean into it, threatens to topple me if I don't. The bitter cold begins to affect me: My fingers, my nose are frozen and it creeps into my arms, threatening my core. One foot in front of the other, I trudge onward.Only a few days ago it was sunny and hot; beautiful flowers, trees, birds, monkeys. Then, I could talk to my friends, chat about inconsequential topics, learn Swahili, point out the landscape and features to one another. It was nice in the rain forest. Now there is only rock: black, pitted and dusty lava rock from this sleeping volcano. Now I can hear nothing but the howling wind; we do not attempt to speak for the effort required. Only a few words at the occasional rest stop, mumbled breathlessly through cracked and frozen lips. We must keep our rhythm, must keep moving to stay warm. We must keep moving.&lt;/p&gt;</summary><content type="html">&lt;p&gt;No moon, no stars, only blackness. Freezing, failing headlamps reveal only a couple of meters of terrain around me. The 60kmph wind howls, cutting off speech and chilling us through to the core; it supports my weight when I lean into it, threatens to topple me if I don't. The bitter cold begins to affect me: My fingers, my nose are frozen and it creeps into my arms, threatening my core. One foot in front of the other, I trudge onward.Only a few days ago it was sunny and hot; beautiful flowers, trees, birds, monkeys. Then, I could talk to my friends, chat about inconsequential topics, learn Swahili, point out the landscape and features to one another. It was nice in the rain forest. Now there is only rock: black, pitted and dusty lava rock from this sleeping volcano. Now I can hear nothing but the howling wind; we do not attempt to speak for the effort required. Only a few words at the occasional rest stop, mumbled breathlessly through cracked and frozen lips. We must keep our rhythm, must keep moving to stay warm. We must keep moving.&lt;/p&gt;
&lt;h2&gt;Day 0&lt;/h2&gt;
&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Sometimes I think I push my luck. Outside the airport in Kenya, I followed a man who offered me a good deal, hopped into an unmarked vehicle, not an official taxi. The driver spoke little english, the streets were dark and unmarked.&lt;/p&gt;
&lt;p&gt;Then onto a neglected but packed shuttle bus. Most of the 270km and nearly 5 hours is spent travelling alongside what may someday, if the corruption lapses, become a great road. For now short stretches of pavement are bookended by a 'barricade' of sticks, logs, rocks and anything else the desert can offer, between long sections of what I'll generously call a jeep trail. The driver takes little notice of the poor road and the bus zooms happily along without slowing.&lt;/p&gt;
&lt;p&gt;Town after desolate, extremely poor town passes by my window. People in traditional garb line the streets playing, talking, selling. Everyone has a stick, the Tanzanian trophy of herding. In between those towns scenery rolls by exactly as I had always imagined Africa: big blue sky, flat-topped trees, flat ground as far as the eye can see.&lt;/p&gt;
&lt;p&gt;We nearly collide with two giraffe but they run out of the way with a grace I had thought impossible for such gawky creatures. As night falls, a glow appears in the distance: we pass another bus, not unlike our own, ablaze and empty, the surrounding darkness hiding any clue to what happened. The heat is intense even through our shuttle's windows and I have to turn away. Miles on we pass a line of pedestrians — from that bus, I wonder?&lt;/p&gt;
&lt;p&gt;I arrive in Arusha and check into my hostel. Small and undecorated, it gets the job done. Tonight I eat at the rooftop restaurant in the shadow of Mt. Meru, reading, writing and ensuring that everything is ready for tomorrow's trek. I watch a fight break out in the street: this is why it is not recommended to walk at night, I think, but to be honest I see little difference between Arusha and Oakland. I savor my final shower, let the hot water relax my travel-weary body. Slowly and deliberately I pack,  tomorrow it begins.&lt;/p&gt;
&lt;h2&gt;Day 1&lt;/h2&gt;
&lt;h3&gt;Rising action&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;5.66km//1066m elevation gain//3021m camp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Breakfast while watching the sun rise over Arusha. My guide picks me up early this morning and as I climb into the Land Cruiser I meet my fellow climbers basically for the first time. We are all excited, happy, sharing stories of our first days in Africa. JT has run four Ironman triathlon and over 20 marathons, Don has run more than he can count. They are both Kilimanjaro veterans; I am feeling a little intimidated.&lt;/p&gt;
&lt;p&gt;We are dropped under the A-Frame of Machame gate with lunch and at least 30 fellow tourist-climbers while the guides and Chagga porters distribute loads and prepare. The Chagga have lived at the base of Kilimanjaro for centuries, accompanying summit attempts since 1890.&lt;/p&gt;
&lt;p&gt;Anxious to begin and a little scared, we wait. Vendors howl and yell from the other side of the fence, trying to sell hats and souvenirs, any last-minute items you may have forgotten or didn't even know you needed. In our tourist alcove we are safe and await the signal from our guides. We shoot a quick photo or two to commemorate the start and get proof that, at some point, we were clean.&lt;/p&gt;
&lt;p&gt;It begins with a whimper: the hike is gentle, easy, slow. Very slow. No, very slow. "Polepole" the guides call out each time I pull ahead: they are asking me to slow down. Their "leisurely" pace is no doubt designed for out-of-shape tourists to whom altitude sickness is a major threat, but down here at 2000m it is only frustrating.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Rainforest&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A sprinkling of rain keeps the forest cool and green. I occupy myself with my camera: walk 100m, take a dozen photos as the others catch up. Here in the rain forest, surrounded by lush vegetation there is no shortage of subjects for me to focus on. Birds caw and I see explosions of bright red and blue feathers as our passage disturbs them. I hear blue monkey all around but never catch sight.&lt;/p&gt;
&lt;p&gt;After a gentle 5.66km we arrive to a fully prepared camp: sleeping tents pitched, our stuff stashed inside, and a dining tent with washwater, fresh popcorn and hot drinks. Such luxury! A few hours rest and a surprisingly ornate and delicious dinner is served. Afterward we are invited to stay in the dining tent for hot drinks and chat with the guide who answers our many questions. At last, "lala salama": sleep safely. Tomorrow we will wake early and have a long day.&lt;/p&gt;
&lt;h2&gt;Day 2&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;3.36km//800m gain//3822m camp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We wake to a brisk, sunny morning with warm washwater, hot drinks served bag-side and a tasty breakfast. The sun is shining warmly  as I stash my gear for the porters to pack and prepare my daypack for the hike.&lt;/p&gt;
&lt;p&gt;"Twende, twende" begins our hike. In contrast to yesterday, uphill is the unvarying theme of the day. We leave the rainforest suddenly and enter the contrasting stubby trees of the heath zone. Shortly after we are rewarded with our first grand view of Mount Kilimanjaro as we pass into the moorland – almost a sharp line separates the trees and knee-high brush. Now there is little shelter from the sun's heat, fortunately the clouds roll in to protect us as the day progresses.&lt;/p&gt;
&lt;p&gt;We get our first glimpse of Uhuru peak, Kilimanjaro’s summit and our ultimate destination. “Uhuru” means “Freedom” in Swahili, so named when Tanzania achieved independence from the British in 1961. “Nzuri sana!” – it is very beautiful. Without the thick forest today’s hike is suffused with panoramic vistas over the Shira Plateau, Kibo, Meru and everywhere in between. The latter half of the day is marked by an occasional scramble up boulders. Obsidian rock is scattered along the ground, occupying my mind in a kind of treasure hunt as we slowly hike upward.&lt;/p&gt;
&lt;p&gt;We camp near Shira Cave. This sooty indentation is a cave in name only, but the surrounding rock makes for excellent bouldering and I play a little before dinner. The short day ends similarly to the last and I crawl into my bag hungering for a little more challenge.&lt;/p&gt;
&lt;h2&gt;Day 3&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;9.92km (?) // 800m ascent, 150m gained // 3970m camp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Today was long and spectacular. An easy but lengthy 10km and 7 hours of walking through vast fields of scattered lava rock in an otherworldly landscape, it felt as if we were walking on the moon. Massive clouds would approach, enveloping us with enchanting effect: visibility would drop to nothing, but the crowds of other hikers and porters would melt into obscurity giving us the entire mountain to ourselves.&lt;/p&gt;
&lt;p&gt;Up, up, up we went until we reached the Lava Tower: a 50' monolith of lava rock, sadly almost totally obscured by the cloud. Only a vague outline could be seen even as I stood at its base after scrambling over some lesser rocks the size of Volkswagens.&lt;/p&gt;
&lt;p&gt;Then down, down down, moving at double time to stay warm and dry as this afternoon's cloud pelted us with hail. As we ran down the ravine the fog occasionally opened up, revealing a vast green garden growing directly from the rock. Feeder streams crossed the mountain, at least a half-dozen small waterfalls followed our path down.&lt;/p&gt;
&lt;p&gt;As we approached camp, tall vegetation again sprung up from the rock, the first green we'd seen in a day or two. These peculiar trees were perhaps taken straight from a Dr. Seuss novel or a scene from Alice in Wonderland. 10m tall, with leaves only at the tips of massive, 1m diameter branches. It takes 25 years to grow one branch, our guide tells us.&lt;/p&gt;
&lt;p&gt;That night, the cloud that followed us throughout the day passed us by and the stars shone with the brilliance of diamonds. Pao.&lt;/p&gt;
&lt;h2&gt;Day 4&lt;/h2&gt;
&lt;h3&gt;Another climb high, camp low day.&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;4.39km // 266m gain // 4018 camp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Every morning begins with a cup of tea delivered to my door and the shining sun to warm up the body and dry our camp. Yes, I could get used to this. The clouds always roll in as afternoon approaches, but the sunny spirits of the morning are never diminished. The first half of the day was a fun scramble, hands and feet, up the Barranco wall. True, these parts were slow as we waited for porters to haul gear, from duffels to stacks of plastic chairs, up over the mountain, but the fun of climbing outweighed any impatience.&lt;/p&gt;
&lt;p&gt;Rolling hills dominated the rest of the day's short trek: up, down, up, down. The Karangu (peanut) huts welcomed us after only 4 hours of walking, and our whole afternoon was dedicated to food and relaxation. Relax, our guides tell us: the next two days will be a test of our bodies and endurance.&lt;/p&gt;
&lt;h2&gt;Day 5&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;4.1km&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Today is the eve of the summit attempt. It is short and uneventful and after the brief hike we nap the afternoon away. My mind wanders and the elevation begins to take effect: the notes I take are nothing but a few sentences of nonsense like “in the mornings, faint but omnipresent murmuring of porters is like the background radiation of the Universe.” Nausea prevents me from eating the night’s dinner and excitement prevents much sleep. We conserve our energy and attempt to nap in preparation for nightfall’s big push.&lt;/p&gt;
&lt;h2&gt;Day 6a&lt;/h2&gt;
&lt;h3&gt;Climax&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;4.5km// 5907m max elevation // 11pm - 9am&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We wake at 10:30pm, still technically day 5, after a scant 5 hours of “sleep.” The temperature outside is well below freezing and the wind howls such that I cannot hear a thing. I wrap myself in every piece of warm clothing I have brought. I can't eat, I'm still queasy from the dinner I skipped. We depart camp early, at 11pm exactly. With wind chill, our guide estimates that the temperature is a -20°F. Ahead of us are the challenges of nearly 7 hours of hiking and 4000ft of elevation gain, the wind and temperature as enemies. The ascent steepens as the wind increases  and the temperature drops with every foot gained. As we approach Hans Meyer Point, the infamous false summit, we face cruel 50mph winds and temperatures below -45°F. Our guide later informs us that winds like these have not been felt on the summit for more than 5 years: we are so lucky. It nearly blows us over as we step up the precarious walkways.&lt;/p&gt;
&lt;p&gt;It is pitch black. No moon, no stars. We can only see the few feet ahead in the tiny bubble of light our freezing and failing headlamps illuminate. We cannot see the summit, the path ahead, nor even the path behind save for the broken line of headlamps following us up the mountain.&lt;/p&gt;
&lt;p&gt;After three or four hours the cold really begins to penetrate. My lips, chin, fingers: they lost feeling long ago. But now, my hands start to chill. I try hand warmers but I can barely feel the warmth they provide. Then the cold moves up into my wrists, then my arms. It is a weird feeling, like water slowly flowing down through rocks. I do my best, stamping and rubbing but the cold is powerful. If we ever stop walking, even for a moment, the cold creeps into my feet, up my legs and threatens my core. I no longer stop moving: at every pause I pace, stamp and move however I can. My hands are permanently clenched around the luke-warm heaters, fingers long ago pulled from the gloves.&lt;/p&gt;
&lt;p&gt;But the pace continues! Around 5am we reach Stella Point, a milestone in itself and stopping point for some. This part is a steep, slow, cold march and a test of our mental and physical endurance. The cold has become nearly intolerable and the elevation steals our intelligence. Don has lost sensibilities: our guide must take him by the arm to guide him up this last section. The wind, taunting us, reaches new levels of gusto. At one point it funnels into a jet through the rocks and attempts to blow us down a sheer rocky cliff, off the mountain and out of existence. We are nearly there, I hope.&lt;/p&gt;
&lt;p&gt;Suddenly, there is an odd wooden sign with a dozen people milling about. My brain is oxygen deprived, muddled by the cold and exhaustion but I slowly realize: this is the Summit! In the dark I dig out my camera. Working the zippers requires me to remove my gloves and within seconds both hands are frozen. I can’t work the clasps or zippers on my pack. But eventually, with much awkwardness, the camera is out and set up. The cold has subdued our celebration and caused a hazy confusion over our minds but we snap photos in front of the sign and rapidly shuffle out as more frozen, confused trekkers vie for a photo-op. It is rushed and without ceremony, leaving me rather confused and unfulfilled.&lt;/p&gt;
&lt;p&gt;Then I am alone. The others in my group have succumbed to the cold and have moved away to start the descent. I stay. I want a panorama, I think. Some scenery. But it is still pitch-black, only a thin ribbon of pink to the east announcing the coming dawn. I wait. The bitter cold doesn't threaten me anymore: the promise of the sun is too great.&lt;/p&gt;
&lt;p&gt;My reward is rapid. Our guide, Peter, has found me again and understands instantly; he tells me to take my time. The the sun begins to rise over the neighboring peak: just a sliver of bright orange at first. It is beautiful. It is beyond beautiful, it is a savior. The temperature seems to instantly spike to tolerable, or maybe it is just my mood lifting in the presence of light. Suddenly I am back, mentally, physically, everything is recharged. I wander the summit, snapping photos of the horizon, the glacier, the ash pit. There is the world to see, and I am at the top of it!&lt;/p&gt;
&lt;p&gt;I am watching the sunrise from the summit of Kilimanjaro, the highest point in Africa. I am overwhelmed with an emotion I cannot identify or describe: I simultaneously laugh and cry. It is overwhelming, it is powerful. Tears stream down my face and freeze on the tip of my nose.I repeat in my mind: I am watching the sunrise from the summit of Kilimanjaro, the highest point in Africa. I accomplished it. I did it. And it was beautiful.&lt;/p&gt;
&lt;p&gt;It is time. I start down, joining my guide. The sun streams down warmth and light, it is almost hot, even the wind has died. Everything is perfect now. The scenery I just walked through enveloped in darkness finally reveals itself, foreign and gorgeous. The walk back is a miserable slip-and-slide on scree but I'm still riding the emotional high: I summited!&lt;/p&gt;
&lt;h2&gt;Afterward&lt;/h2&gt;
&lt;h3&gt;Dénouement&lt;/h3&gt;
&lt;p&gt;Down, down, down. We walk and walk and walk. The walk is rough, destroying my knees. It is long and boring and after nearly 16 hours of walking and thousands of feet we make camp. At this final camp I learn the secret to sleeping well on the hardest ground through the coldest nights: exhaustion.&lt;/p&gt;</content></entry><entry><title>Burden of hope</title><link href="https://brettsbeta.com/blog/2015/01/22/burden-of-hope/" rel="alternate"/><published>2015-01-22T00:00:53+00:00</published><updated>2015-01-22T00:00:53+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-01-22:/blog/2015/01/22/burden-of-hope/</id><summary type="html">&lt;p&gt;Ice, extending behind us as far as we could see, crunched under our crampon laden feet. It had been a long day — 12 km across the glacier – but now the edge was in sight, and just beyond that a boat bobbed in the ocean awaiting our return.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ice, extending behind us as far as we could see, crunched under our crampon laden feet. It had been a long day — 12 km across the glacier – but now the edge was in sight, and just beyond that a boat bobbed in the ocean awaiting our return.&lt;/p&gt;
&lt;p&gt;We stripped the crampons off, stowed our ice axes and walked across the dirty glacial edge to the pier, then down to the boat. The wind picked up on the water but we couldn't tear our eyes from the massive 20m cliff of ice to head below decks.&lt;/p&gt;
&lt;p&gt;Our guide had disappeared, leaving us to meditate the sight, but soon reappeared bearing a small tray with glasses. Handing one to each of us, he explained: "Argentine Whiskey, the best in the world. That ice – it is 300 years old and taken from the center of the glacier we just crossed."&lt;/p&gt;
&lt;p&gt;As we sipped, Stefanie's eyes met mine with the same thought: it couldn't get better than this. The next two weeks would prove us wrong.&lt;/p&gt;</content></entry><entry><title>KegMon</title><link href="https://brettsbeta.com/blog/2015/01/15/kegmon/" rel="alternate"/><published>2015-01-15T00:00:03+00:00</published><updated>2015-01-15T00:00:03+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-01-15:/blog/2015/01/15/kegmon/</id><summary type="html">&lt;p&gt;If I'm entertaining even a few friends Murphy's law always expresses itself as "If guests are counting on beer, the keg will run out". Clearly more data (and more beer) is the solution to this problem: today we build a device to monitor the quantity of beer left in my Kegerator.&lt;/p&gt;</summary><content type="html">&lt;p&gt;If I'm entertaining even a few friends Murphy's law always expresses itself as "If guests are counting on beer, the keg will run out". Clearly more data (and more beer) is the solution to this problem: today we build a device to monitor the quantity of beer left in my Kegerator.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="media/20150115000003/DSC_1887.jpg"&gt;&lt;/p&gt;
&lt;h1&gt;Background&lt;/h1&gt;
&lt;p&gt;Simply stated, I need a way to determine the amount of beer left inside a keg. As an average beer is 16ounces I'd like to be able to measure differences of less than an ounce. One could weigh the keg to determine the quantity left; however, I initially dismissed using weight as I assumed available sensors were too inaccurate. One could count pours using a simple switch on the tap handle, but this does not account for partial pours or top-offs and seems far too inaccurate. Alternatively, one can monitor the quantity of beer as it is poured to determine what is left.&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# alt text #&amp;gt;" src="media/20150115000003/IMG_1867.jpeg" title="IMG_1867.jpeg"&gt;
The Swissflow800 inline flow monitor is incredibly accurate: it measures around .164mL increments with little calibration or optimization. It covers flow velocities of 0.5 to 20 liter per minute at temperatures of -20 to +90 °C and an operating pressure of up to 230psi (16bar or 1.6MPa) - it seemed perfect. Sadly, the accuracy comes with tradeoffs. First and foremost the sensor is expensive at around $150. There are more prohibitive drawbacks: for each 0.164mL of fluid, the Swissflow sends a pulse which the micro controller must track and count. At the flow rate of beer even my ArduinoMega couldn't keep up. I let the Arduino count every 10 ticks (1.64mL) before running some math, then 100 (16.4mL) but no luck. There are ways of making this work but with the price tag on the SwissFlow I decided this path was not optimal.&lt;/p&gt;
&lt;p&gt;How to get an accurate weight of an object as it varies from 160lbs to 30lbs? Fortunately, MeasSpec recently released the FX1901 load cell, touted as a "1% load cell device with full scale ranges of 10, 25, 50 or 100 and 200lbf compression" with "breakthrough price/performance value". I picked one up from Servoflo.com for about $30 and tested it out: at the initial setup, it detected smaller than half-ounce differences, far better than I had hoped, and far better than the spec sheet indicates!&lt;/p&gt;
&lt;h1&gt;Hardware&lt;/h1&gt;
&lt;h2&gt;Parts List&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Particle Photon the WiFi enabled Arduino-ish microcontroller&lt;/li&gt;
&lt;li&gt;Load cell FX1901 from MeasSpec, purchased from Servoflo.com&lt;/li&gt;
&lt;li&gt;Burr-Brown INA125P instrumentation amplifier, available from Mouser (necessary because load cells only produce a very small change in voltage) Amplifier amplification is set via a simple resistor across pins 8 and 9. I started with 1kΩ, it gave me good results so I didn't try to optimize any further.&lt;/li&gt;
&lt;li&gt;Electronics enclosure, 3d printed&lt;/li&gt;
&lt;li&gt;Some stainless steel framing angles from McMaster-Carr to build the stand&lt;/li&gt;
&lt;li&gt;Audio jack. Not required, I like to wire into a three connection audio jack for easy assembly and disassembly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Electronics assembly is easy. Follow the data sheets on the load cell and instrumentation amplifier, or mimic this Fritzing diagram:
&lt;img alt="&amp;lt;# alt text #&amp;gt;" src="media/20150115000003/KegMonFritzing_bb-e1417560658618.png" title="KegMonFritzing_bb-e1417560658618.png"&gt;&lt;/p&gt;
&lt;p&gt;The stand is very simple; a better stand will be a future exercise, for now this one is functional. An average keg weighs 160lbs when full and around 30lbs when empty. To use a 100lbf sensor we need to relieve some of that load, but still apply a proportional amount. A quick triangular frame built with framing angles left over from a previous project will balance the load nicely across three feet – one of which is the load sensor. In the event there is more force (such as during installation) the load cell can handle a 250% overload, so just try not to drop it more than a couple inches onto the stand.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="media/20150115000003/DSC_1892.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The load must be directly applied to the nub in the middle of the load sensor. While not glamorous, the same 5/16" bolt used for assembly fit nicely onto the center tip without touching any edges. There isn't a lot of lateral movement of a keg after installation so there isn't a big need to secure the sensor at this time.&lt;/p&gt;
&lt;h1&gt;Software&lt;/h1&gt;
&lt;p&gt;Once the hardware is hooked up, a very simple program reads the load cell's amplified analog signal from the Photon's pin 0. The response from the load cell is linear, linear interpolation can be used to get a value using two known loads (I used empty and full):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb nb-Type"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newReading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;voltage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amplified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;
&lt;span class="nb nb-Type"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;bLoad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aLoad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bReading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aReading&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newReading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aReading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aLoad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;predefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;calibration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;interpolate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;aLoad&lt;/code&gt; and &lt;code&gt;bLoad&lt;/code&gt; are known loads, 30lbs and 160lbs respectively, while &lt;code&gt;aReading&lt;/code&gt; and &lt;code&gt;bReading&lt;/code&gt; are pre-recorded sensor readings at those loadings.&lt;/p&gt;
&lt;p&gt;Unfortunately, the load sensor's output is noisy. For now, I query the analog pin almost continuously then average those readings to get a representative value. Eventually I want to use a Kalman filter for better accuracy. After removing noise, the final value is posted to a web service every 'interval' seconds using Particle's &lt;code&gt;TCPClient&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;millis&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mytime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;interval&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;weight&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="c1"&gt;; // Average the readings taken over the last interval&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;sprintf&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/postdata.php?user=0001&amp;amp;weight=%2.2f&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;weight&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;getrequest&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="c1"&gt;; //send to server&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;delay&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;mytime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;millis&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="c1"&gt;; //reset all the values for the next post interval&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The php script records all the data posted to a database or flat file. From here, the data can be manipulated in some fun ways – an exercise I'll leave to the reader. My guests always enjoy knowing their average ounces per hour consumed, how much the flow rate spikes at halftime versus in the middle of a big play, or beers per month consumed. 'Estimated time until empty' is one of my favorite metrics for parties and judging how popular a particular home-brew recipe is.&lt;/p&gt;
&lt;p&gt;A sample graph of a few pourings is below, in which you can see the accuracy of the load cell:&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;lt;# alt text #&amp;gt;" src="media/20150115000003/Screen-Shot-2014-12-03-at-12.13.25-PM.png" title="Screen-Shot-2014-12-03-at-12.13.25-PM.png"&gt;&lt;/p&gt;
&lt;h1&gt;Final Considerations&lt;/h1&gt;
&lt;p&gt;I'll be making some improvements. The stand needs some work, as does the algorithm to remove noise. I've also noticed a slight drift over time, generally upwards, by about 4-6 ounces over an evening. I want to figure out if this is caused by temperature or settling or some other factor, and see if I can filter it out.&lt;/p&gt;
&lt;p&gt;More photos are available here:&lt;/p&gt;
&lt;p&gt;Email or tweet me if you have questions.&lt;/p&gt;
&lt;p&gt;See it running live on our kegerator: &lt;www.4drillsbrewery.com/kegmon/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="media/20150115000003/DSC_1877.jpg"&gt;&lt;/p&gt;</content></entry><entry><title>Truck Box Light</title><link href="https://brettsbeta.com/blog/2015/01/08/truck-box-light/" rel="alternate"/><published>2015-01-08T00:00:16+00:00</published><updated>2015-01-08T00:00:16+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-01-08:/blog/2015/01/08/truck-box-light/</id><summary type="html">&lt;p&gt;A couple years ago my truck was broken into: window smashed, stereo ripped out (joke's on them, it was broken). It made me realize how often I stow thousands of dollars worth of climbing, biking or camping gear in the cab vulnerable to an opportunist thief or a curious bear attracted by an overlooked gel packet. I needed a better solution.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A couple years ago my truck was broken into: window smashed, stereo ripped out (joke's on them, it was broken). It made me realize how often I stow thousands of dollars worth of climbing, biking or camping gear in the cab vulnerable to an opportunist thief or a curious bear attracted by an overlooked gel packet. I needed a better solution.&lt;/p&gt;
&lt;p&gt;I spent far more time researching than necessary and eventually, convinced by excellent reviews and a couple cheesy youtube videos, purchased the WeatherGuard. The box is excellent but when dark out – I invariably pull into my campsite well past midnight – it is impossible to see into even with a headlamp. After a more cursory search online for lighting solutions, I decided to build my own and, of course, to have a little fun with it.&lt;/p&gt;
&lt;h1&gt;Hardware&lt;/h1&gt;
&lt;h2&gt;Parts List&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Arduino UNO. I meant to do this using the Pro Micro but while soldering I accidentally melted it a little.&lt;/li&gt;
&lt;li&gt;1 Sealed non-addressable 1 meter long RGB LED strip with 60 5060 LEDs per meter. Enclosed by a flexible silicon jacket with an IP65 waterproof rating to protect the LEDs, and multicolor to boot!&lt;/li&gt;
&lt;li&gt;3 Fairchild FQP30N06L MOSFETs&lt;/li&gt;
&lt;li&gt;Arduino enclosure and 3D printed insert to hold a button (sorry, no part number, it was something found in my toolbox).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The electronics are wired like above.&lt;/p&gt;
&lt;p&gt;As you can see, I've wired the control lines into the PWM slots of the Arduino, as I'll explain later this is so we can have some fun.&lt;/p&gt;
&lt;p&gt;The LEDs require 12V and fortunately the Arduino can handle that internally so we don't need separately regulated supplies. Most vehicles have ample 12VDC sources – I found a place to splice in the radio circuit. This source is hot only in ACC mode so I can't accidentally burn the truck's battery by leaving the light on while the box is closed. I ran two wires from here back through the truck frame to the truck box, secured using many zip ties.&lt;/p&gt;
&lt;p&gt;The LED strip comes with an adhesive back, simply stick it to the lid of the truck box angled down into the box when the lid is fully open. Some two sided tape on the enclosure and a couple zip ties to secure the 12V wires made quick work of installation.&lt;/p&gt;
&lt;h1&gt;Software&lt;/h1&gt;
&lt;p&gt;I could have made a white light shine into the box without using an Arduino, MOSFETs, or really anything other than a simple switch and the 12V source. It would have been cheaper but it would not have been as fun or as functional.&lt;/p&gt;
&lt;p&gt;First of all I want a red light for night time use in addition to white. Second, simply turning the light on and off is too jarring so I will use the Arduino's PWM pins to quickly fade the light on and off. Here I've programmed a quick button press for white light and an extended button press to turn on the red light:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;CODE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;m really sorry, I have temporarily lost the code and only have the compiled version. I&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;looking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gone&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we need to take advantage of this strip's ability to be any color at all: Keep holding the button for 3 seconds to enter DiscoBrett(tm) mode. This is just the first revision, eventually I want to augment this code to blink instead of simply change, wire it into the speakers and have it adjust brightness based on volume, or maybe something more.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;CODE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;m really sorry, I have temporarily lost the code and only have the compiled version. I&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;looking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gone&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Demonstration&lt;/h1&gt;
&lt;p&gt;&lt;a href="#"&gt;TruckBoxLight from Brett Heliker on Vimeo.&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Future&lt;/h1&gt;
&lt;p&gt;As long as I have an Arduino in my truck, what else can I do? Some initial ideas:
- Bluetooth to my phone for control
- Temperature, humidity, environment detection
- Accelerometer to record shocks while driving/offroading
- Light detection and lid opening detection so the light automatically turns on&lt;/p&gt;
&lt;p&gt;That's all for now. Have any ideas for me? Send me a message.&lt;/p&gt;</content></entry><entry><title>My 2014</title><link href="https://brettsbeta.com/blog/2015/01/01/my-2014/" rel="alternate"/><published>2015-01-01T00:00:56+00:00</published><updated>2015-01-01T00:00:56+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2015-01-01:/blog/2015/01/01/my-2014/</id><content type="html">&lt;p&gt;My 2014 annual summary: http://brettsbeta.com/my2014/&lt;/p&gt;</content></entry><entry><title>What I learned in Germany</title><link href="https://brettsbeta.com/blog/2012/12/13/what-i-learned-in-germany/" rel="alternate"/><published>2012-12-13T05:20:00+00:00</published><updated>2012-12-13T05:20:00+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2012-12-13:/blog/2012/12/13/what-i-learned-in-germany/</id><summary type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It does not exist for you. You exist for it: You have come because it exists.&lt;/li&gt;
&lt;li&gt;Travel creates context to inspire thought. Life creates context for thought.&lt;/li&gt;
&lt;li&gt;The best journeys answer questions you never even thought to ask.&lt;/li&gt;
&lt;li&gt;Everything Is Illuminated in the light of the past (Great book)&lt;/li&gt;
&lt;li&gt;Your convictions should be strong, so long as they do not blind you to their opposition: Appreciate the sunset even while knowing the sky is blue.&lt;/li&gt;
&lt;li&gt;Maya Angelou once said you can tell a lot about a person from the way they respond to three things: a rainy day, tangled Christmas lights, and lost luggage.&lt;/li&gt;
&lt;li&gt;“The only way to be truly satisfied is to do what you believe is great work, and the only way to do great work is to love what you do.“ -Steve Jobs&lt;/li&gt;
&lt;/ul&gt;</content></entry><entry><title>What I learned in Iceland</title><link href="https://brettsbeta.com/blog/2011/11/27/what-i-learned-in-iceland/" rel="alternate"/><published>2011-11-27T05:36:00+00:00</published><updated>2011-11-27T05:36:00+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2011-11-27:/blog/2011/11/27/what-i-learned-in-iceland/</id><summary type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some rough roads smooth out shortly after they start, others end completely after a few km.&lt;/li&gt;
&lt;li&gt;The literature of the world has borrowed a lot from this tiny nation.&lt;/li&gt;
&lt;li&gt;Clouds look awesome with a wide-angle lens. People do not.&lt;/li&gt;
&lt;li&gt;Visit a Glacier or two in you life. It is worth it.&lt;/li&gt;
&lt;/ul&gt;</content></entry><entry><title>What I learned in Patagonia</title><link href="https://brettsbeta.com/blog/2011/08/23/what-i-learned-in-patagonia/" rel="alternate"/><published>2011-08-23T05:31:00+00:00</published><updated>2011-08-23T05:31:00+00:00</updated><author><name>Brett Heliker</name></author><id>tag:brettsbeta.com,2011-08-23:/blog/2011/08/23/what-i-learned-in-patagonia/</id><summary type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Travel creates opportunity for reflection and thought. On each trip, I jot down notes of my daydreams, conversations, and overheard moments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How you present yourself is not how people will perceive you.&lt;/li&gt;
&lt;li&gt;Opportunities are like photographs: the only ones regretted are those not taken.&lt;/li&gt;
&lt;li&gt;Life is good when sipping whiskey served over 300 year old ice chipped from a glacier you walked across. (Photo)&lt;/li&gt;
&lt;li&gt;Try to look approachable because you never will approach.&lt;/li&gt;
&lt;li&gt;Cultivate those moments that help you remember what it feels like to care rather than just do.&lt;/li&gt;
&lt;li&gt;Sometimes it takes the similarities of another person to see yourself accurately.&lt;/li&gt;
&lt;li&gt;New company can reveal sensitivities you thought were long healed.&lt;/li&gt;
&lt;li&gt;pending all day, everyday with a virtual stranger can teach you a lot. About them, you, prejudices, preferences, people around you, everything.&lt;/li&gt;
&lt;li&gt;Wine, bread, cheese and some meat is delicious. Fruit is also delicious.&lt;/li&gt;
&lt;/ul&gt;</content></entry></feed>