<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Unreal Expectations]]></title><description><![CDATA[Technology & Development That Works...  Mostly]]></description><link>https://michaelleo.com/</link><image><url>https://michaelleo.com/favicon.png</url><title>Unreal Expectations</title><link>https://michaelleo.com/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Fri, 03 Apr 2026 22:31:12 GMT</lastBuildDate><atom:link href="https://michaelleo.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Zoom Busy Light Automation]]></title><description><![CDATA[How do I let the home office visitors know when a Zoom call is occurring]]></description><link>https://michaelleo.com/zoom-busy-light/</link><guid isPermaLink="false">5f1dc24a13a9a000017c7cda</guid><category><![CDATA[home automation]]></category><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Fri, 11 Sep 2020 00:32:42 GMT</pubDate><media:content url="https://michaelleo.com/content/images/2020/07/IMG_2735.JPG" medium="image"/><content:encoded><![CDATA[<img src="https://michaelleo.com/content/images/2020/07/IMG_2735.JPG" alt="Zoom Busy Light Automation"><p>Individually addressable LEDs provide a simple way to get status of things around the home without having to pull out phone or looking at a dashboard view. &#xA0;LEDs are visible from 10 or 20 feet away making them easy to see at a glance. &#xA0;I have a couple of <a href="https://shop.homeseer.com/collections/z-wave-dimmers-switches/products/z-wave-dimmer-switch">Homeseer WD-200+ Z-Wave switches</a> installed with one of them is located in a hallway near the bedroom. &#xA0;As locks change state, the LEDs on the switch turn green (unlocked) or red (locked). </p><p>This project came out of wanting to have even more LEDs available on some device that can show status of the house. &#xA0;For example, could I show the status of every window&apos;s sensor having an open or closed state. &#xA0;As I searched around for programmable LEDs, the Raspberry Pi came up for <a href="https://shop.pimoroni.com/products/unicorn-hat-mini">various</a> <a href="https://shop.pimoroni.com/products/raspberry-pi-sense-hat">options</a> of <a href="https://thepihut.com/collections/waveshare/products/waveshare-rgb-led-phat-4-x-8">programmable</a> LEDs. &#xA0;Looking at Amazon specifically, I came across many <a href="https://duckduckgo.com/?q=amazon+busy+light&amp;t=hk&amp;ia=shopping&amp;iax=shopping">busy light products</a> which inspired the idea. &#xA0;Did this idea already exist for the Pi? &#xA0;<a href="https://www.eliostruyf.com/diy-building-busy-light-show-microsoft-teams-presence/">Of course</a> it did. </p><p>The following does not present an exhaustive list of the steps to build what I built, but tries to present enough information and direction to allow someone else to extend and improve. &#xA0; </p><!--kg-card-begin: markdown--><h2 id="workingfromhome">Working From Home</h2>
<p>I have been working from home for the last two years and six of the last eight years.  So the work from home part and use of Zoom didn&apos;t change much when the California Stay at Home order came down in March 2020.  What changed was the home office going from a solitary work environment to a shared environment with four other people.  What changed was having conference calls with no interruptions to kids needing help with something at the most inconvient times. Letting members of the family know when I cannot be interrupted makes it easier to manage those interruptions</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="hardwareforbusylight">Hardware for Busy Light</h2>
<ul>
<li><a href="https://shop.pimoroni.com/products/pi-zero-w-starter-kit">Pi Zero W Starter Kit</a> - Came with everything needed to get up and running with the Pi Zero and 1x8 Blinkt LEDs</li>
<li><a href="https://shop.pimoroni.com/products/phat-diffuser">pHAT Diffuser</a> - The LEDs can be really bright, this helps with diffusing the lights and makes them look nicer</li>
</ul>
<h3 id="ledalternates">LED Alternates</h3>
<ul>
<li><a href="https://shop.pimoroni.com/products/unicorn-phat">Unicorn pHAT</a> - A a 4x8 matrix of LEDs.  Requires soldering or <a href="https://shop.pimoroni.com/products/pogo-a-go-go-solderless-gpio-pogo-pins">GPIO Pogo Pins</a> or <a href="https://shop.pimoroni.com/products/gpio-hammer-header?variant=35643317962">GPIO Hammer Header (Solderless)</a></li>
<li><a href="https://shop.pimoroni.com/products/unicorn-hat-mini">Unicorn HAT Mini</a> - Does not require soldering</li>
</ul>
<p>My initial work uses the Starter Kit and Blinkt light, but a newer edition is using the GPIO Hammer Header woth Unicorn pHAT.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="softwarearchitecture">Software Architecture</h2>
<h3 id="zoom">Zoom</h3>
<p>We use Zoom at work for remote meetings.  This is probably a bit unsurprising since most of the world seemingly started using Zoom. Zoom offers APIs to access their services. They also support <a href="https://marketplace.zoom.us/docs/api-reference/webhook-reference">Webhooks</a> that will send your service notification messages when events occur. I am interested in hooking into changes to my presence.</p>
<p><strong>Presence Events</strong><br>
The webhook system will send presence events for the following events</p>
<ul>
<li>When a meeting is manually started or ended</li>
<li>When a meeting on the calendar starts or ends</li>
<li>When status in the Zoom app is manually changed</li>
<li>When the Away timer triggers</li>
</ul>
<h4 id="internetaddressibility">Internet Addressibility</h4>
<p>A challenge that comes up often with webhooks is that the callback endpoint of the webhook has to be Internet accessible.  My Pi Light will live in my home network and so need a path for the notification messages from Zoom to make it into my network and to the Pi.</p>
<ul>
<li>One option is enabling port forwarding on my router and making the Pi directly accessible from the the Internet. This would also require a Dynamic DNS service so the webhook can find my Internet IP address.</li>
<li>Another option uses a tunneling service such as ngrok.com to provide a public HTTPS endpoint that tunnels to a localhost port. Either I pay for the service to get my own subdomain, or deal with potential of the address changing over time. Ngrok is great for prototyping, but seems problematic for long term use.</li>
<li>Finally, looking at what I already have running at home and Internet hosted, the following items stuck out
<ul>
<li>Home Network
<ul>
<li>MQTT Server</li>
<li>Homebridge</li>
</ul>
</li>
<li>Hosted Server
<ul>
<li>Nginx</li>
<li>Let&apos;s Encrypt Certificate</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The following then became the vision for the architecture</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-full"><img src="https://michaelleo.com/content/images/2020/07/busy-light-architecture-1.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1446" height="583" srcset="https://michaelleo.com/content/images/size/w600/2020/07/busy-light-architecture-1.png 600w, https://michaelleo.com/content/images/size/w1000/2020/07/busy-light-architecture-1.png 1000w, https://michaelleo.com/content/images/2020/07/busy-light-architecture-1.png 1446w"></figure><!--kg-card-begin: markdown--><h3 id="mqttserver">MQTT Server</h3>
<p>The usage of MQTT Server is a recent addition to my home automation stack.  MQTT is a lightweight publish/subscribe protocol built for Internet of Things connectivity.  <a href="http://mosquitto.org">Mosquitto</a> is the server then used for for MQTT brokering.  Clients can connect to Mosquitto and publish messages and/or subscribe to receive messages.</p>
<p>Mosquitto supports authentication, authorization and TLS-based communication.  In other words, access to the server can be secured.</p>
<h4 id="mosquittobridging">Mosquitto Bridging</h4>
<p>MQTT is built for IoT.  Bridging allows one Mosquitto server to connect to another server and publish messages, subscribe to messages or both. This allows software to communicate to a server in their local environment and process messages that branch out to multiple end-points without having to worry about how many other servers and networks might exist behind the curtain.<br>
In this usage scenario, my home network Mosquitto server can connect to the hosted Mosquitto server utilizing an outbound network connection.  It can then relay messages from the hosted instance to any clients on the local network.</p>
<p>The home network remains closed to inbound connections.</p>
<h3 id="nginxserver">Nginx Server</h3>
<p><a href="https://www.nginx.com">Nginx</a> is a web server but will be used for reverse proxy HTTP services and general TCP proxy services.  While Nginx may know what HTTP traffic is, it can also proxy the MQTT traffic to the hosted Mosquitto server via the TCP proxy.<br>
Nginx supports TLS termination on HTTP or TCP connections. Integrate with Let&apos;s Encrypt support and we have easy certificate generation and renewal.</p>
<p>In this usage scenario, Nginx provides the TLS termination for enabling secure Internet traffic and will proxy HTTP/Webhook traffic and MQTT/Mosquitto traffic to the underlying services on the server.</p>
<h3 id="homebridgemqttthingplugin">Homebridge &amp; mqtt-thing Plugin</h3>
<p><a href="https://homebridge.io">Homebridge</a> emulates the Homekit API. Plugins to Homebridge can integrate with 3rd party APIs and expose the devices to Homekit. In my initial architecture, Homebridge was run directly on the Pi and this was fine.  However, since the Pi Zero only has 512 MB of RAM and running multiple Node applications could strain the memory or swap.  I already have an instance of Homebridge running on a Mac Mini, the existing instance could handle the additional plugin with little additional overhead.</p>
<h4 id="mqttthingplugin">MQTT-Thing Plugin</h4>
<p><a href="https://github.com/arachnetech/homebridge-mqttthing">MQTT-Thing</a> is a Homebridge plugin that allows integration of many different accessory types using MQTT. Using the plugin, I can expose an RGB Light to Homekit and when interactions with the light occur in the Home app or Homekit routines, the plugin will publish messages that can be listened for and update the LEDs appropriately.</p>
<h3 id="customnodeapps">Custom Node Apps</h3>
<p>There are two custom node apps.  One runs on the public server, handles the Zoom Webhooks and publishes the presence state to the MQTT server.  The other runs on the Pi, handles the MQTT messages from Zoom or Homekit and updates LEDs.</p>
<h4 id="zoomwebhookpublishing">Zoom Webhook &amp; Publishing</h4>
<p>The Zoom Webhook code is based on the following samples from Zoom, <a href="https://github.com/zoom/zoom-oauth-sample-app">Zoom Oauth Sample</a> and <a href="https://github.com/zoom/zoom-sample-webhookapp">Zoom Webhook Sample</a>.  In my Zoom account configuration, the Webhook Only configuration is not available by default, but OAuth option is available. Therefore, the application had to support OAuth to register access to my Zoom account and then used a Webhook to receive the presence change events.  As the presence events come in, the data is reformatted and published to MQTT.</p>
<h4 id="mqttlistenerpilight">MQTT Listener &amp; Pi Light</h4>
<p>The MQTT message listener and Pi LED light control application registers a subscriber to MQTT for two different topics, one for Zoom and one for Homebridge. When a message is received, it is parsed for Zoom or Homebridge commands and the command is then executed.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://michaelleo.com/content/images/2020/09/BusyLightMonitor.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="2000" height="1125" srcset="https://michaelleo.com/content/images/size/w600/2020/09/BusyLightMonitor.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/BusyLightMonitor.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/BusyLightMonitor.png 1600w, https://michaelleo.com/content/images/2020/09/BusyLightMonitor.png 2213w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><h2 id="stepstosuccess">Steps to Success</h2>
<h3 id="1acquireaccesstointernetserver">1. Acquire Access to Internet Server</h3>
<p>Use what you have or use <a href="https://www.linode.com">one</a> <a href="https://www.digitalocean.com">of</a> <a href="https://cloud.google.com/">the</a> <a href="https://azure.microsoft.com/">myriad</a> <a href="https://aws.amazon.com">virtual</a> <a href="https://neoserver.site">server</a> providers to access a server.</p>
<p>I used <a href="https://www.linode.com">Linode</a> for this purpose.</p>
<h3 id="2setupthesoftwareoninternetserver">2. Setup the Software on Internet Server</h3>
<p>I used docker / docker-compose to install and configure the individual server software for Nginx &amp; Mosquitto</p>
<p>There are many source available for installing and configuring Nginx with Let&apos;s Encrypt.</p>
<ul>
<li><a href="https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71">https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71</a></li>
<li><a href="https://www.cloudbooklet.com/how-to-install-nginx-and-lets-encrypt-with-docker-ubuntu-20-04/">https://www.cloudbooklet.com/how-to-install-nginx-and-lets-encrypt-with-docker-ubuntu-20-04/</a></li>
</ul>
<p>Running Mosquitto within Docker</p>
<ul>
<li><a href="https://blog.feabhas.com/2020/02/running-the-eclipse-mosquitto-mqtt-broker-in-a-docker-container/">https://blog.feabhas.com/2020/02/running-the-eclipse-mosquitto-mqtt-broker-in-a-docker-container/</a></li>
</ul>
<p>To set up the MQTT reverse proxy and TLS termination for MQTT, see the following:</p>
<ul>
<li><a href="https://www.nginx.com/blog/nginx-plus-iot-load-balancing-mqtt/">https://www.nginx.com/blog/nginx-plus-iot-load-balancing-mqtt/</a></li>
<li><a href="https://www.nginx.com/blog/nginx-plus-iot-security-encrypt-authenticate-mqtt/">https://www.nginx.com/blog/nginx-plus-iot-security-encrypt-authenticate-mqtt/</a></li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="3createthezoomoauthwebhookpublisher">3. Create the Zoom OAuth/Webhook &amp; Publisher</h3>
<p>The node app built off of Zoom&apos;s sample application code and then extended it for my purposes.  The initial steps get the application up and running to receive the Zoom events via a webhook.  The last steps connect it to the Mosquito server to publish the events.</p>
<p>The sample app instruction suggest using ngrok.com, which is fine for development purposes.  A later step in the process includes bundling the application into node and proxying through the nginx server.</p>
<p>Step 1.  Follow the example Readme at <a href="https://github.com/zoom/zoom-oauth-sample-app">Zoom Oauth Sample</a> to create the OAuth application.</p>
<p>Step 2. Add Webhook Endpoint in the Node Express application following the code at <a href="https://github.com/zoom/zoom-sample-webhookapp">Zoom Webhook Sample</a>.</p>
<p>Step 3. Update the  Configurations of the OAuth application in Zoom Marketplace to enable the Event Subscriptions.</p>
<p>Step 3.a In the Feature tab of the OAuth app Configuration, enable Event Subscriptions and add the endpoint URL created in Step 2.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/09/Zoom_02_Features.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1962" height="1866" srcset="https://michaelleo.com/content/images/size/w600/2020/09/Zoom_02_Features.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/Zoom_02_Features.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/Zoom_02_Features.png 1600w, https://michaelleo.com/content/images/2020/09/Zoom_02_Features.png 1962w" sizes="(min-width: 720px) 720px"></figure><p></p><!--kg-card-begin: markdown--><p>Step 3b. Under the Feature, add the Event Types for User and User Activity so the Webhook will receive the Events when presence changes.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/09/Zoom_03a_Events.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1962" height="1098" srcset="https://michaelleo.com/content/images/size/w600/2020/09/Zoom_03a_Events.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/Zoom_03a_Events.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/Zoom_03a_Events.png 1600w, https://michaelleo.com/content/images/2020/09/Zoom_03a_Events.png 1962w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/09/Zoom_03b_Events.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1966" height="1106" srcset="https://michaelleo.com/content/images/size/w600/2020/09/Zoom_03b_Events.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/Zoom_03b_Events.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/Zoom_03b_Events.png 1600w, https://michaelleo.com/content/images/2020/09/Zoom_03b_Events.png 1966w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Step 3c. Ensure scopes are set properly. Minimally, the User Information read and write permissions are required.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/09/Zoom_04_Scopes.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1824" height="1878" srcset="https://michaelleo.com/content/images/size/w600/2020/09/Zoom_04_Scopes.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/Zoom_04_Scopes.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/Zoom_04_Scopes.png 1600w, https://michaelleo.com/content/images/2020/09/Zoom_04_Scopes.png 1824w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>You may need to reactivate the app with the node application to ensure it has proper scopes and webhook is fully activated.  The webhook should be receiving the presence messages.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Step 4. Publish the messages to mosquitto</p>
<p>The mqtt module was added to the node application. I then created a status.js file and Status class that contained the connection and publshing to mqtt.</p>
<p>The events that Zoom sends are not necessarily in order that they occur.  Occasionally, an event is received that is older than a previous event received.  The code in handlePresenceEvent keeps track of the last event and ensures the latest received event has a dateTime that is newer before the event is published.</p>
<pre><code>const moment = require(&apos;moment&apos;);
const mqtt=require(&apos;mqtt&apos;);
const storage = require(&apos;node-persist&apos;);
let path = require(&apos;path&apos;);

let appDir = path.dirname(require.main.filename);

let addDebugging = () =&gt; {

};

class Status {
    constructor({debug, mqtt:{url, options}}) {
        this.isInit = false;
        this.lastEvent = undefined;

        this.client = mqtt.connect(url,options)

        this.client.on(&quot;connect&quot;,function() {
            console.log(&quot;connected&quot;);
        });
        this.client.on(&quot;error&quot;,function(error){ console.log(&quot;Can&apos;t connect&quot;+error);});

        if (debug) {
            addDebugging();
        }

    }

    async init() {
        await storage.init({
            dir: `${appDir}/storage`
        });
        this.lastEvent = await storage.getItem(&apos;lastEvent&apos;);
        if (this.lastEvent &amp;&amp; this.lastEvent.dateTime) {
            this.lastEvent.dateTime = moment(this.lastEvent.dateTime);
        } else {
            this.lastEvent = {};
        }
        this.isInit = true;
    }

    async handlePresenceEvent(event) {
        if (!this.isInit) {await this.init()}

        if (event.event !== &apos;user.presence_status_updated&apos; ||
            !(event.payload &amp;&amp; event.payload.object)) {
            return;
        }

        let eventObject = event.payload.object;

        let normal = {
            dateTime: moment(eventObject.date_time),
            email: eventObject.email,
            profileId: eventObject.id,
            presence: eventObject.presence_status
        }

        if (!this.lastEvent.dateTime || this.lastEvent.dateTime.isBefore(normal.dateTime)) {
            this.lastEvent = normal;
            await storage.setItem(&apos;lastEvent&apos;, this.lastEvent);
            console.log(&quot;Updating last presence status&quot;);

            if (this.client.connected) {
                let topic = `zoom/${normal.profileId}/presence`;
                this.client.publish(topic, normal.presence);
                console.log(&quot;published: &quot;, topic, normal.presence);
            }
        } else {
            console.log(&quot;Ignoring presence status&quot;);
        }
    }
}

module.exports = Status;

</code></pre>
<p>And the Webhook sample method was updated to call the handlePresenceEvent() method instead of dealing with Webinar events.</p>
<pre><code>// Set up a webhook listener for your Webhook Event - in this case we are listening to Presence and other events
app.post(&apos;/webhook&apos;, bodyParser.raw({ type: &apos;application/json&apos; }), async (req, res) =&gt; {

    let event;

    try {
        event = JSON.parse(req.body);
    } catch (err) {
        res.status(400).send(`Webhook Error: ${err.message}`);
        console.log(&quot;Failed to parse body&quot;, err);
        return;
    }
    // Check to see if you received the event or not.
    console.log(event)
    if (req.headers.authorization === process.env.verificationToken) {
        res.status(200);

        if (event.event === &apos;user.presence_status_updated&apos;) {
            console.log(&quot;Presence Webhook Received.&quot;)
            status.handlePresenceEvent(event).catch(reason =&gt; {
                console.log(&quot;handlePresenceEvent threw exception&quot;, reason);
            })
        }

        res.send();

    } else {

        res.status(403).end(&apos;Access forbidden&apos;);
        console.log(&quot;Invalid Post Request.&quot;)
    }
});

</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Step 5. Add node.js app to docker container &amp; proxy the nginx secure port</p>
<ul>
<li><a href="https://nodejs.org/en/docs/guides/nodejs-docker-webapp/">https://nodejs.org/en/docs/guides/nodejs-docker-webapp/</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose">https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose</a></li>
</ul>
<h3 id="publicsidecomplete">Public Side Complete</h3>
<p>At this point, the left side of the diagram is complete.  The Zoom notifications should be received by the the Internet server and published to an MQTT server.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="4mosquittoonprivatenetwork">4. Mosquitto on Private Network</h3>
<p>I set up a separate instance of Mosquitto in bridge mode on a Mac Mini in my local network. This was done since I already had Mosquitto running and it prevented the need of the Raspberry Pi Zero to connect to the Internet accessible Mosquitto server.  However, this step is a bit superfluous and the Node app on the Pi can connect directly to the public Mosquitto server.</p>
<ul>
<li><a href="http://www.steves-internet-guide.com/mosquitto-bridge-configuration/">http://www.steves-internet-guide.com/mosquitto-bridge-configuration/</a></li>
</ul>
<p>mosquitto.conf Bridge Configuration</p>
<pre><code>connection bridge-node
address example.com:8883
remote_username &lt;mqtt_user&gt;
remote_password &lt;mqtt_password&gt;

topic # in 0 &quot;&quot; &quot;&quot;
bridge_cafile /usr/local/etc/mosquitto/DST_Root_CA_X3.pem
</code></pre>
<p>The cafile for DST_Root_CA_X3.pem had to be downloaded and installed on my server. The certificate is a root certificate that Let&apos;s Encrypt&apos;s intermediate certificate files have been cross-signed against.  This ensures that when the Mosquitto server connects over SSL it can verify the certificate it receives from Nginx. (Read)[<a href="https://letsencrypt.org/certificates/">https://letsencrypt.org/certificates/</a>] on the Let&apos;s Encrypt certificates and signing.  There are other options for Root certificates.</p>
<h3 id="5createmqttlistenerpilightnodeapp">5. Create MQTT Listener &amp; Pi Light Node App</h3>
<p>The overall code base behind the MQTT listener and process to update the light is much larger than what is presented.  I am planning on providing a UI to directly update the light from a Web Page, but that is not built yet.  Most of the code to handle the webhook events and homebridge events condenses into 4+ files:</p>
<ul>
<li>App startup</li>
<li>MQTT Listener</li>
<li>Common LED Class</li>
<li>LED Implementation Binding Class</li>
</ul>
<h4 id="appstartup">App Startup</h4>
<pre><code>// app.js
// Bring in environment secrets through dotenv
require(&apos;dotenv/config&apos;)
const Listener = require(&apos;./lib/listener.js&apos;);
const ledImpl = process.env.ledImpl || &apos;pi-null-led&apos;;
const deviceId = process.env.deviceId || &apos;pi-zero-wh&apos;;

let listener = new Listener({
  deviceId,
  ledImpl,
  debug: true,
  mqtt: {
    url: process.env.mqttURL,
    options: {
      username: process.env.mqttUsername,
      password: process.env.mqttPassword
    }
  }
});
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="mqttlistener">MQTT Listener</h4>
<pre><code>// lib/listener.js
const mqtt=require(&apos;mqtt&apos;);

class Listener {
  constructor({deviceId, ledImpl, debug, mqtt:{url, options}}) {
    const {createLedHandler} = require(`./${ledImpl}.js`);

    this.deviceId = deviceId || &apos;pi-zero-wh&apos;;

    this.led = createLedHandler();

    this.lastEvent = undefined;

    this.client = mqtt.connect(url,options);


    this.client.on(&quot;connect&quot;,function() {
      console.log(&quot;connected&quot;);

      this.client.subscribe([&quot;zoom/#&quot;, `home/busylight/${this.deviceId}/#`], [], (err, granted) =&gt; {
        console.log(&quot;Subscribe:&quot;)
        console.log(&quot;  Error: &quot;, err);
        console.log(&quot;  Granted: &quot;, JSON.stringify(granted, null, 2));
      });
    }.bind(this));

    this.client.on(&quot;error&quot;,function(error){
      console.log(&quot;Can&apos;t connect&quot;+error);
    });

    this.client.on(&apos;message&apos;, this.handleMessage.bind(this));
    
  }


  async handleMessage(topic, message) {
    console.log(&quot;handleMessage: &quot; + JSON.stringify({topic, message: message.toString()}, null, 2));
    if (topic.startsWith(&quot;zoom/&quot;)) {
      let {rgb: [red, green, blue]} = await this.led.setPresence(message.toString());

      // Send stat message to MQTT Thing to update the state
      const pubTopic = `home/busylight/${this.deviceId}/light/stat/rgb`;
      const pubMessage = `${red},${green},${blue}`;
      
      this.client.publish(pubTopic, pubMessage);

    } else if (topic.startsWith(&quot;home/&quot;)) {
      this.handleHomeMessage(topic, message.toString());
    }
  }

  async handleHomeMessage(topic, message) {
    //console.log(`topic.indexOf(&quot;/cmd/&quot;): ` + (topic.indexOf(&quot;/cmd/&quot;) &gt; 0));

      if (topic.indexOf(&quot;/cmd/&quot;) &gt; 0) {
        //console.log(`topic.indexOf(&quot;/rgb&quot;): ` + (topic.indexOf(&quot;/rgb&quot;) &gt; 0));
        if (topic.indexOf(&quot;/rgb&quot;) &gt; 0) {
          const regex = /^\d\d?\d?,\d\d?\d?,\d\d?\d?$/;
          //console.log(`message.match(regex): ` + message.match(regex));
          if (message.match(regex)) {
            let json = `[${message}]`;
            let rgb = JSON.parse(json);
            //console.log(`message.match(regex): ${json}`);

            this.led.setColor({rgb});
            this.client.publish(topic.replace(&apos;cmd&apos;, &apos;stat&apos;), message);
          }

        } else if (topic.indexOf(&quot;/power&quot;) &gt; 0) {
          if (topic.indexOf(&quot;/zoom&quot;) &gt; 0) {
            this.led.enablePresenceUpdates(message.toString() !== &quot;true&quot;);
            this.client.publish(topic.replace(&apos;cmd&apos;, &apos;stat&apos;), message);
          }
          if (topic.indexOf(&quot;/light&quot;) &gt; 0) {
            this.led.setPower({power: message.toString() === &quot;true&quot;});
            this.client.publish(topic.replace(&apos;cmd&apos;, &apos;stat&apos;), message);
          }
        }
      } else if (topic.indexOf(&quot;/stat/&quot;) &gt; 0) {

      }

  }
}

module.exports = Listener;

</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="commonledclass">Common LED Class</h4>
<p>The Led class is a base class that handles most of the common logic of interpreting the message and keeping track of the last state of the light.  The child classes then interact with the native node modules to update the light based on the common settings.</p>
<pre><code>// lib/led.js
const storage = require(&apos;node-persist&apos;);
let path = require(&apos;path&apos;);

let appDir = path.dirname(require.main.filename);

class Led {
  constructor() {
    this.isInit = false;
    this.disablePresence = false;
  }

  async init() {
    await storage.init({
      dir: `${appDir}/storage`
    });
    this.isInit = true;
  }

  async setPresence(presence) {
    if (!this.isInit) {await this.init()}

    const {enable} = (await storage.getItem(&apos;enablePresenceUpdates&apos;)) || {enable: true};

    if (!enable) {
      return;
    }

    let color  = {
      rgb: [0, 0, 0],
      clear: false,
      lowlight: true
    }

    if (presence.toLowerCase() === &apos;away&apos;) {

      color.rgb = [255, 255, 0];
    } else if (presence.toLowerCase() === &apos;available&apos;) {
      color.rgb = [0, 255, 0];
    } else if (presence.toLowerCase() === &apos;do_not_disturb&apos;) {
      color.rgb = [255, 0, 0];
    }

    await this.setColor(color);

    return color;

  }

  async setColor(color) {
    if (!this.isInit) {await this.init()}

    console.log(&quot;Set color to :&quot; + JSON.stringify(color));

    await storage.setItem(&apos;color&apos;, color);
  }

  async enablePresenceUpdates(enable) {
    if (!this.isInit) {await this.init()}

    this.disablePresence = !enable;
    //console.log(`enablePresenceUpdates(enable): ${enable}`)
    await storage.setItem(&apos;enablePresenceUpdates&apos;, {enable});

  }

  async setPower({power}) {
    if (!this.isInit) {await this.init()}

    let color = await storage.getItem(&apos;color&apos;);
    if (!color) {
      color = {
        rgb: [255, 255, 255],
        clear: false,
        lowlight: true
      };
    }

    if (power) {
      color.clear = false;
    } else {
      color.clear = true;
    }
    await this.setColor(color);

    await storage.setItem(&apos;power&apos;, {power});
  }

}

module.exports = Led;

</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="blinktledclass">Blinkt LED Class</h4>
<p>Implementation for the Blink LED</p>
<pre><code>// lib/pi-blinkt-led.js
let Led = require(&apos;./led.js&apos;);
let Blinkt = require(&apos;node-blinkt&apos;);

class BlinktLed extends Led {
    constructor() {
        super();

        try {
            this.leds = new Blinkt();

        } catch (err) {
            console.log(err);
            this.leds = {
                setAllPixels: () =&gt; {},
                clearAll: () =&gt; {}
            }
        }
    }

    async setColor(color) {
        let {rgb: [red, green, blue], clear, lowlight} = color;

        await super.setColor(color)

        if (clear) {
            this.leds.clearAll();
        } else {
            this.leds.setAllPixels(red, green, blue, (typeof lowlight !== &apos;undefined&apos; &amp;&amp; lowlight === true) ? 0.4 : 1.0);
        }
    }

}

module.exports = {
    createLedHandler : () =&gt; new BlinktLed(),
};
</code></pre>
<h4 id="unicornphatclass">Unicorn Phat Class</h4>
<p>Implementation for the Unicorn Phat module LED</p>
<pre><code>// lib/pi-unicorn-phat-led.js
let Led = require(&apos;./led.js&apos;);

class UnicornPhatLed extends Led {

    constructor() {
        super();
        this.numberLeds = 32;

        try {
            this.leds = require(&apos;rpi-ws281x-native&apos;);
            this.leds.init(this.numberLeds, {dmaNum: 10});
            this.leds.setBrightness(25);

        } catch (err) {
            console.log(err);
            this.leds = {
                setAllPixels: () =&gt; {},
                clearAll: () =&gt; {}
            }
        }
    }

    async setColor(color) {
        let {rgb: [red, green, blue], clear, lowlight} = color;
        let pixel = (green &lt;&lt; 16) | (red &lt;&lt; 8) | blue;
        console.log(pixel.toString(16));

        let pixels = [];
        for (let i = 0; i &lt; this.numberLeds; i++) {
            pixels.push(pixel);
        }

        await super.setColor(color)

        if (clear) {
            this.leds.reset();
        } else {
            this.leds.setBrightness((typeof lowlight !== &apos;undefined&apos; &amp;&amp; lowlight === true) ? 10 : 100);
            this.leds.render(pixels);
        }
    }

}

module.exports = {
    createLedHandler : () =&gt; new UnicornPhatLed(),
};

</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="6bonushomebridgemqttthingplugin">6. Bonus Homebridge &amp; MQTT-Thing Plugin</h3>
<p>I initially set up Homebridge on the Pi Zero itself to keep it separate from the existing instance.  However, with so little RAM on the Pi Zero, it took a while to start up and probably started using a bit of swap.  So I relented and setup the plugin on the primary Homebridge instance.</p>
<p>Since the Node app is already listening to the MQTT server, it was straight forward to add topics to the MQTT plugin and have the node app process those messages separately from the Zoon topic.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/09/IMG_3022.PNG" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="1125" height="2436" srcset="https://michaelleo.com/content/images/size/w600/2020/09/IMG_3022.PNG 600w, https://michaelleo.com/content/images/size/w1000/2020/09/IMG_3022.PNG 1000w, https://michaelleo.com/content/images/2020/09/IMG_3022.PNG 1125w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h4 id="examplemqttpluginconfig">Example MQTT Plugin Config</h4>
<pre><code>&quot;accessories&quot;: [
        {
            &quot;accessory&quot;: &quot;mqttthing&quot;,
            &quot;url&quot;: &quot;mqtt://example.local:1883&quot;,
            &quot;username&quot;: &quot;&lt;local-mqtt-user&gt;&quot;,
            &quot;password&quot;: &quot;&lt;local-mqtt-password&gt;&quot;,
            &quot;mqttOptions&quot;: {
                &quot;keepalive&quot;: 120
            },
            &quot;logMqtt&quot;: true,
            &quot;type&quot;: &quot;custom&quot;,
            &quot;name&quot;: &quot;Busy Light Zero&quot;,
            &quot;services&quot;: [
                {
                    &quot;type&quot;: &quot;lightbulb-RGB&quot;,
                    &quot;name&quot;: &quot;Busy Light&quot;,
                    &quot;topics&quot;: {
                        &quot;getOnline&quot;: &quot;home/busylight/pi-zero-wh/light/stat/lwt&quot;,
                        &quot;getRGB&quot;: &quot;home/busylight/pi-zero-wh/light/stat/rgb&quot;,
                        &quot;setRGB&quot;: &quot;home/busylight/pi-zero-wh/light/cmd/rgb&quot;,
                        &quot;getOn&quot;: &quot;home/busylight/pi-zero-wh/light/stat/power&quot;,
                        &quot;setOn&quot;: &quot;home/busylight/pi-zero-wh/light/cmd/power&quot;
                    }
                },
                {
                    &quot;type&quot;: &quot;switch&quot;,
                    &quot;name&quot;: &quot;Zoom Presence Override&quot;,
                    &quot;topics&quot;: {
                        &quot;getOnline&quot;: &quot;home/busylight/pi-zero-wh/zoom/stat/lwt&quot;,
                        &quot;getOn&quot;: &quot;home/busylight/pi-zero-wh/zoom/stat/power&quot;,
                        &quot;setOn&quot;: &quot;home/busylight/pi-zero-wh/zoom/cmd/power&quot;
                    }
                }
            ]
        }
    ]
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://michaelleo.com/content/images/2020/09/BusyLightMonitorBlue.png" class="kg-image" alt="Zoom Busy Light Automation" loading="lazy" width="2000" height="1125" srcset="https://michaelleo.com/content/images/size/w600/2020/09/BusyLightMonitorBlue.png 600w, https://michaelleo.com/content/images/size/w1000/2020/09/BusyLightMonitorBlue.png 1000w, https://michaelleo.com/content/images/size/w1600/2020/09/BusyLightMonitorBlue.png 1600w, https://michaelleo.com/content/images/size/w2400/2020/09/BusyLightMonitorBlue.png 2400w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<p>This was a fun project giving me a chance to explore the Raspberry Pi Zero and build on some other technologies I had in place to automate the home.</p>
<p>The amount of custom code was small versus the library reuse and building on the strong work of others.</p>
<h3 id="whatsnext">What&apos;s Next</h3>
<p>The Zoom webhooks are great for receiving the events when you are connected to Zoom and access the Zoom APIs.  This is great as a passive notification system for others to see.</p>
<p>Rather than reactive, I want the light to be proactive and act as reminder to the kids that they have a Zoom meeting scheduled for online class with their teacher.  The zoom meeting times are now on a shared calendar.  If I can get the Pi to read the calendar and change colors and play a sound when the meetings start, I can more easily ensure the kids are where they should be.To support this, I am looking at integrating Microsoft Office 365 Graph APIs to read the calendar.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Backyard Cafe Lights]]></title><description><![CDATA[When I added cafe lights to define space in the backyard, I added automation control to programmatically turn them on and off.]]></description><link>https://michaelleo.com/automating-backyard-cafe-lights/</link><guid isPermaLink="false">5ee3212ea43264000173496d</guid><category><![CDATA[home automation]]></category><category><![CDATA[backyard]]></category><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Mon, 15 Jun 2020 15:36:35 GMT</pubDate><media:content url="https://michaelleo.com/content/images/2020/06/CafeLights-7.png" medium="image"/><content:encoded><![CDATA[<h3 id="about-the-space">About the Space</h3><img src="https://michaelleo.com/content/images/2020/06/CafeLights-7.png" alt="Backyard Cafe Lights"><p>Our backyard has a concrete patio. &#xA0;A previous owner added it, ostensibly to install a hot tub, though the hot tub never seemed to have been put in. &#xA0;We have always used the patio for furniture. &#xA0;</p><p>The cafe lights were added after a rehabilitation of the backyard. &#xA0;We remodeled a part of the house and during the construction the backyard was trampled upon, used for storage and generally neglected for about 18 months. &#xA0;The backyard had gone from a nice, water wasting, grass filled area to a dilapidated dirt pile. &#xA0;We worked with landscaper to design and clean up the backyard.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://michaelleo.com/content/images/2020/06/CafeLights2.png" class="kg-image" alt="Backyard Cafe Lights" loading="lazy"><figcaption>x</figcaption></figure><p>We wanted to improve the concrete patio and decided to add some cafe lights to frame the space. &#xA0;After looking at some ideas and options on how to hang the lights in the middle of the space, we used <a href="https://www.homedepot.ca/en/home/ideas-how-to/lighting/diy-string-light-pole.html">DIY project</a> from HomeDepot&apos;s site.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://youtu.be/sqW-Nyo_dFw"><div class="kg-bookmark-content"><div class="kg-bookmark-title">DIY: String Light Poles</div><div class="kg-bookmark-description">Brighten your backyard with these DIY string light poles. Using concrete, lumber and string lights, you can illuminate your yard in style. For more informati...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://youtu.be/yts/img/favicon_144-vfliLAfaB.png" alt="Backyard Cafe Lights"><span class="kg-bookmark-author">YouTube</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://i.ytimg.com/vi/sqW-Nyo_dFw/hqdefault.jpg" alt="Backyard Cafe Lights"></div></a></figure><p>We purchased a set of <a href="https://www.amazon.com/gp/search/ref=as_li_qf_sp_sr_tl?ie=UTF8&amp;tag=unrealexpecta-20&amp;keywords=B072L11FJ6&amp;index=aps&amp;camp=1789&amp;creative=9325&amp;linkCode=ur2&amp;linkId=a9035999c746e6080cd6ab1c29183a6f">Enbrighten Cafe Lights</a> to use for the lighting. &#xA0;The string has 24 lights across 48 feet of cabling. &#xA0;The Cafe Lights have a remote control to turn it on/off or change the colors. &#xA0;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://youtu.be/0phhgaQ8ABc"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Enbrighten Vintage Seasons Color Changing Cafe Lights</div><div class="kg-bookmark-description">Set the perfect mood for every occasion with Enbrighten Seasons LED color changing cafe lights &#x2013; celebrate holidays, cheer on your favorite teams, or enjoy a...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://youtu.be/yts/img/favicon_144-vfliLAfaB.png" alt="Backyard Cafe Lights"><span class="kg-bookmark-author">YouTube</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://i.ytimg.com/vi/0phhgaQ8ABc/hqdefault.jpg" alt="Backyard Cafe Lights"></div></a></figure><h3 id="home-automation">Home Automation</h3><p>Although the cafe lights have a remote control to turn the lights on and off and change the colors, the lights retain their state when power is removed and reapplied to the lights. &#xA0;If the lights are powered on with the remote and power is removed, it will turn back on when power is applied once again.</p><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/06/CafeLightsPlug.png" class="kg-image" alt="Backyard Cafe Lights" loading="lazy"></figure><p>Keeping the automation simple, I use a <a href="https://www.amazon.com/GE-Weather-Resistant-Required-Works-SmartThings-14284/dp/B06W9NWFM3">GE Outdoor ZWave Plug</a>. &#xA0;A common plug I have used for the outdoor automations elsewhere. &#xA0;The longest active plug of this brand has been in use for 7 years with most of them being 3 or 4 years old at this point. The only issue I have had with any of these plugs is that the little plug cover eventually wears down from the weather and falls off. &#xA0;</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Event Trigger</th>
<th>Event</th>
</tr>
</thead>
<tbody>
<tr>
<td>Scheduled @ 30 minutes before sunset</td>
<td>Turn On</td>
</tr>
<tr>
<td>Scheduled @ midnight</td>
<td>Turn Off</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>The event triggers are just kept to a schedule. &#xA0; Keeping it at 30 minutes before sunset ensures that it is on before darkness sets in or when it is an overcast day. &#xA0;Midnight is an optimum time to turn it off since it is with a 95% confidence that we will not in the backyard after midnight. </p><h3 id="homekit">HomeKit</h3><p>While they are automated to turn on and off, there are times when it is necessary to manually change their state. They are exposed to HomeKit in the Backyard room.</p><figure class="kg-card kg-image-card"><img src="https://michaelleo.com/content/images/2020/06/HomeKit.jpg" class="kg-image" alt="Backyard Cafe Lights" loading="lazy"></figure><h2 id="what-s-next-what-s-missing">What&apos;s Next / What&apos;s Missing</h2><p>The Enbrighten Cafe Lights come with a remote control that enables changing the colors of the lights or setting them to one of many preset color rotations. &#xA0;At the moment, they do not provide any out of the box solution to automate or program the color selection. &#xA0;My hardware hacking skills are not up to snuff, so I will have to wait for Enbrighten to develop some programmable remote or another company to offer something similar.</p>]]></content:encoded></item><item><title><![CDATA[Network Closet & Awesome Home Network]]></title><description><![CDATA[<!--kg-card-begin: html--><p><strong>Network Closet</strong><br>
When I first purchased our new house, I wasn&#x2019;t aware that a previous owner had updated various outlets in the house and run ethernet cable from the outlets to a closet. &#xA0;We never looked too close at this closet to see that the cables were</p>]]></description><link>https://michaelleo.com/home-network-2013/</link><guid isPermaLink="false">5ee6a02993ec950001b02668</guid><category><![CDATA[home network]]></category><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Mon, 14 Oct 2013 22:09:00 GMT</pubDate><media:content url="https://michaelleo.com/content/images/2020/06/network_closet-900x1200-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><img src="https://michaelleo.com/content/images/2020/06/network_closet-900x1200-1.jpg" alt="Network Closet &amp; Awesome Home Network"><p><strong>Network Closet</strong><br>
When I first purchased our new house, I wasn&#x2019;t aware that a previous owner had updated various outlets in the house and run ethernet cable from the outlets to a closet. &#xA0;We never looked too close at this closet to see that the cables were all running together.</p>
<p>Unfortunately, who ever installed it did all the hard work of running the cables to the closet, but never finished the work of connecting the cables to the patch panel. &#xA0;Although, they did have two cables sliced together to allow port to port connectivity.</p>
<p>Not too long after moving in, I had a professional come in, install some home theatre equipment and fix the network. &#xA0;They fixed the patch panel connecting up the existing network cables. &#xA0;They installed three new runs of cables from the kids&#x2019; room, living room, and family room to the closet. &#xA0;They also relocated the Verizon ONT Ethernet cable from the office area to the network closet.</p>
<p>To have power in the closet, I had an electrician install a new outlet in the top of the closet connected to an existing outlet in the bedroom from the other side of the closet wall.</p>
<p>I now have eight hard-wired ports throughout the house. &#xA0;One each in two of the bedrooms, one in office, one in the living room, two in the kitchen, one in the play room, and one in the family room. &#xA0; &#xA0;Seven of the eight ports are connected to an 8-port Gigabit switch with the last port on the switch uplinked to the Verizon FiOS router. &#xA0; The Verizon FiOS router is connected via ethernet cable to the ONT.</p>
<p><strong>Broadband Internet</strong><br>
I work from home using the network pretty much all day. &#xA0;At night the kids like to watch shows on Netflix or iTunes on the AppleTV. &#xA0;Work and play at our house utilizes a lot of bandwidth. &#xA0;After starting to work from home in the last house, I upgraded our Verizon Service from 35Mbps to 150Mbps. &#xA0;This caused a lot of problems at the old house.</p>
<p>We had Verizon FiOS installed as soon as their systems registered it was available for the house, around mid-2007. &#xA0;We therefore had an early ONT box (optical network terminal that converts the fiber into twisted-pair telephone, coax, and ethernet) and the box had to be upgraded to even handle the faster Internet speed. &#xA0;The FiOS router had to be replaced as well since the switch on the router only supported 100Mbps and would be too slow for the broadband. &#xA0;Newer FiOS routers have gigabit switches and so this was replaced. &#xA0;Last, the WAN coax was swapped for ethernet cable. &#xA0; The installer had a lot of trouble switching the ONT boxes and getting our account setup with the new one, including the phone, video, and network. &#xA0;But once it was all setup, the 150 Mbps speed was great. &#xA0;Generally, things download quickly.</p>
<p>When we moved, one of the considerations for new house was having Verizon FiOS available. &#xA0;I just don&#x2019;t think I could easily go back to the Time Warner cable. &#xA0;Verizon hasn&#x2019;t yet put in any soft bandwidth caps and I would certainly hit them every now and then. &#xA0;I use <a href="http://www.code42.com/crashplan/">Crashplan</a> to back up all of our pictures, music and movies off site and a full backup of these pushes 1 TB when I first started and 1.4 TBs now. &#xA0;The new house also has Verizon FiOS and the move process went relatively smooth getting the phone moved to the new line and the set tops / cablecard recognized for the new account. &#xA0;There wasn&#x2019;t a good place to install the battery backup, but eventually came up with an OK place to install as it had to be installed indoors out of weather. &#xA0;It was eventually installed behind an access door to the power supply to the jet spa tub.</p>
<p><img class="alignright" alt="Network Closet &amp; Awesome Home Network" src="http://www.speedtest.net/result/3177964846.png"></p>
<p>We continue to have great 150Mbps download from Verizon. &#xA0;It isn&#x2019;t 1Gpbs that Google is offering in the few places they offer, but it is pretty rare to consider the service disappointing. &#xA0;The only faster option Verizon has at the moment is 300Mpbs, but can&#x2019;t justify the price on that yet. &#xA0;The 150Mbps easily allows kids to watch their own Netflix Super HD streams while I still do other things on the computer. In three months, I don&#x2019;t feel we have been restricted by our broadband.</p>
<p><strong>Awesome Local Network</strong><br>
Having the great Internet bandwidth only works with local network not getting clogged by communication of different devices. &#xA0;This is what makes the ethernet cabling so great. &#xA0;I can more easily segment the traffic and not overburden the WiFi network. &#xA0;For example, the home theatre has TiVo Roamio, Samsung Smart TV, and AppleTV that each can use ethernet or WiFi. &#xA0;In addition, the receiver has ethernet for Internet radio, Airplay support or network remote functionality. &#xA0;&#xA0;I have laptop I use most of the day, build server, media server and laptop for Carrie and Kids. &#xA0;There are many more devices, such as Home Automation server, couple more AppleTVs, Time Machine backup server, another Airport Extreme and TiVo Mini. &#xA0;There are the Samsung Smart Washer and Dryer with network access. &#xA0;Finally, there are multiple tablets and phones.</p>
<p>If all these devices were trying to access a single WiFI network, especially during video streaming, it would suck, even though I have dual band wireless-N routers available. &#xA0;By moving most stationary devices to ethernet I lighten the load on the WiFi-only devices and they can make better use of the network as well.</p>
<p>Having the switch in the network closet only makes this better. &#xA0;Devices that need to communicate can generally do it with less interference from other devices. &#xA0;For example, we have TiVo Mini that pairs with the TiVo Roamio. &#xA0;The Mini uses one of tuners on the Roamio to watch live TV. &#xA0;The Mini can change channels by which the Roamio will use a tuner to decode the cable data and then stream the video the Mini which presents on screen. &#xA0;The Roamio sends the network data to the switch which then sends it to the Mini all across ethernet, not affecting WiFi usage. &#xA0;Further, the switch will move the data from the Roamio to the Mini without impacting other devices on other ports. &#xA0;My laptop can browse the Internet which will send data to the switch which is then sent to the uplink to the router, not interfering with the Roamio/Mini data stream. &#xA0; The switch on the Router isn&#x2019;t used and all traffic on the uplink cable is only data that is going to or coming from the Internet. &#xA0;&#xA0;The Roamio to Mini don&#x2019;t necessarily have full use of the line, however; there is a second switch with the home theatre equipment. &#xA0;The TiVo, AppleTV, TV, Receiver, and an Airport Extreme are plugged in to a switch with the uplink going go the switch in the network closet. &#xA0;With the second switch also a gigabit switch, the devices aren&#x2019;t going to create too much interference with the other devices. &#xA0; &#xA0;There is a third switch in the office that has 3 computers, the Time Machine, and the home automation server.</p>
<p>The 1Gbps LAN is great and fast. &#xA0;I copied a 2GB file from old laptop to new one. &#xA0;I was able to get to a near sustained transfer rate of 117 MBps or 936 Mbps. &#xA0;That&#x2019;s pretty awesome network performance.</p>
<p>With the ethernet and switch, I am able to have two (currently) WiFi access points connected at different points in the house. &#xA0;With simultaneous dual band 80211.N the remaining devices on the network are never too far from an access point. &#xA0;The tablets work almost as good as the other devices for streaming video.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[XMPPFramework / iOS / libidn / -all_load failure]]></title><description><![CDATA[<!--kg-card-begin: html--><p>As I integrated the <a href="https://github.com/robbiehanson/XMPPFramework">XMPPFramework</a> it requires the <a href="http://www.gnu.org/s/libidn/">libidn</a> library for a couple of methods it uses.&#xA0; The latest version of libidn that the XMPPFramework included as of this writing is 1.15.</p>
<p>Unfortunately, this version seems incompatible with the &#x201C;Other Linker Flag&#x201D; -all_load that other</p>]]></description><link>https://michaelleo.com/xmppframework-ios-libidn-all_load-failure/</link><guid isPermaLink="false">5ee6a02993ec950001b02667</guid><category><![CDATA[iOS Development]]></category><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Sun, 04 Dec 2011 01:02:01 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>As I integrated the <a href="https://github.com/robbiehanson/XMPPFramework">XMPPFramework</a> it requires the <a href="http://www.gnu.org/s/libidn/">libidn</a> library for a couple of methods it uses.&#xA0; The latest version of libidn that the XMPPFramework included as of this writing is 1.15.</p>
<p>Unfortunately, this version seems incompatible with the &#x201C;Other Linker Flag&#x201D; -all_load that other libraries seem to want to use.&#xA0; The all_load linker flag appears be a work around for a linker bug that pops up when object files within a static library only contain a category and no classes.</p>
<p>From the source: <a href="http://developer.apple.com/mac/library/qa/qa2006/qa1490.html">http://developer.apple.com/mac/library/qa/qa2006/qa1490.html</a></p>
<blockquote><p>IMPORTANT: For 64-bit and iPhone OS applications, there is a linker bug  that prevents -ObjC from loading objects files from static libraries  that contain only categories and no classes. The workaround is to use  the -all_load or -force_load  flags. -all_load forces the linker to load  all object files from every archive it sees, even those without  Objective-C code. -force_load is available in Xcode 3.2 and later. It  allows finer grain control of archive loading. Each -force_load option  must be followed by a path to an archive, and every object file in that  archive will be loaded.</p></blockquote>
<p>Interestingly, most coding conventions will create header and implementation files for categories that are just the categories; hence exposing the defect.&#xA0; Some library developers have come up with a simple workaround so that the force_load or all_load flags are not required.&#xA0; The workaround is a simple macro that is added to the category implementation file that just creates a class so that the bug is not exposed.&#xA0; Don&#x2019;t know if it originated in Three20, but this is <a href="https://github.com/facebook/three20/pull/406">first place</a> I came across it.&#xA0; RestKit also <a href="https://github.com/RestKit/RestKit/issues/239">implemented</a> the macro.</p>
<p>Unfortunately, other library developers have not yet implemented this, and still require the all_load flag to be used.</p>
<p>The use of the all_load flag has been causing the XMPPFramework&#x2019;s use of libidn to fail to link.&#xA0; In part this appears to be the use of older libidn that is included as fat binary,&#xA0; static library.&#xA0; Use of the all_load flag often causes this linker error:</p>
<blockquote><p>Undefined symbols for architecture i386:<br>
&#x201C;_libintl_bindtextdomain&#x201D;, referenced from:<br>
_idna_strerror in libidn.a(strerror-idna.o)<br>
_pr29_strerror in libidn.a(strerror-pr29.o)<br>
_punycode_strerror in libidn.a(strerror-punycode.o)<br>
_stringprep_strerror in libidn.a(strerror-stringprep.o)<br>
_tld_strerror in libidn.a(strerror-tld.o)<br>
&#x201C;_libintl_dgettext&#x201D;, referenced from:<br>
_idna_strerror in libidn.a(strerror-idna.o)<br>
_pr29_strerror in libidn.a(strerror-pr29.o)<br>
_punycode_strerror in libidn.a(strerror-punycode.o)<br>
_stringprep_strerror in libidn.a(strerror-stringprep.o)<br>
_tld_strerror in libidn.a(strerror-tld.o)<br>
&#x201C;_libiconv&#x201D;, referenced from:<br>
_str_cd_iconv in libidn.a(striconv.o)<br>
_mem_cd_iconv in libidn.a(striconv.o)<br>
&#x201C;_libiconv_open&#x201D;, referenced from:<br>
_str_iconv in libidn.a(striconv.o)<br>
&#x201C;_libiconv_close&#x201D;, referenced from:<br>
_str_iconv in libidn.a(striconv.o)<br>
ld: symbol(s) not found for architecture i386<br>
clang: error: linker command failed with exit code 1 (use -v to see invocation)</p></blockquote>
<p>The iconv errors can be fixed because the iOS framework includes libiconv.a.&#xA0; It does not include libintl.a.&#xA0; After downloading version 1.25 of libidn and getting it to install on iOS 5.0, I was able to get rid of errors related to intl.</p>
<p>Here are the commands I used to compile libidn.1.25 and create a fat binary with arm6, arm7, and i386 architecture files.</p>
<p><code><br>
$ ./configure --host=arm-apple-darwin --disable-shared CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-gcc-4.2 CFLAGS=&quot;-arch armv7 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs -fpascal-strings -O0 -Wreturn-type -Wunused-variable -Wunused-value -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -fvisibility=hidden -gdwarf-2 -mthumb -miphoneos-version-min=4.0 &quot; CPP=/Developer/Platforms/iPhoneOS.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-cpp-4.2 AR=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ar<br>
$ make clean<br>
$ make<br>
$ cp lib/.libs/libidn.a libidn-arm7.a</code></p>
<p>$ ./configure --host=arm-apple-darwin --disable-shared CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-gcc-4.2 CFLAGS=&quot;-arch armv6 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs -fpascal-strings -O0 -Wreturn-type -Wunused-variable -Wunused-value -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -fvisibility=hidden -gdwarf-2 -mthumb -miphoneos-version-min=4.0 &quot; CPP=/Developer/Platforms/iPhoneOS.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-cpp-4.2 AR=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ar<br>
$ make clean<br>
$ make<br>
$ cp lib/.libs/libidn.a libidn-arm6.a</p>
<p>$ ./configure --host=i686-apple-darwin --disable-shared CC=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-gcc-4.2 CFLAGS=&quot;-arch i386 -fmessage-length=0 -pipe -std=c99 -Wno-trigraphs -fpascal-strings -O0 -Wreturn-type -Wunused-variable -Wunused-value -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk -fvisibility=hidden -gdwarf-2 -mthumb -miphoneos-version-min=4.0 &quot; CPP=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/llvm-gcc-4.2/bin/llvm-cpp-4.2 AR=/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/ar<br>
$ make clean<br>
$ make<br>
$ cp lib/.libs/libidn.a libidn-i386.a</p>
<p>$ lipo -create libidn-i386.a libidn-arm6.a libidn-arm7.a -output libidn.a<br>
</p>
<p>After importing the new libidn,a, I still received the linking errors with libiconv.&#xA0; Added that to the library link list and afterwards all worked.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Multiple Language Meta Descriptions / One Page]]></title><description><![CDATA[<!--kg-card-begin: html--><p>During a post implementation design review today, the design and implementation failed to meet the basic one requirement of the enhancement.  The requirement was to ensure the site in question would respond to Katakana characters when searched through google.co.jp.   The english title of the site is returned quite</p>]]></description><link>https://michaelleo.com/multiple-language-meta-descriptions-one-page/</link><guid isPermaLink="false">5ee6a02993ec950001b02666</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Tue, 17 May 2011 15:53:41 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>During a post implementation design review today, the design and implementation failed to meet the basic one requirement of the enhancement.  The requirement was to ensure the site in question would respond to Katakana characters when searched through google.co.jp.   The english title of the site is returned quite high, but when using the Katakana characters it fails.</p>
<p>The site in question is english only and so it mostly makes sense that it fails in google search results for Katakana.  So the requirement was how to make the site appear utilizing Katakana.  </p>
<h2>Technical Solution<br>
</h2>
<p>The developer went off and designed and implemented a solution.  The solution relied upon the Accept-Language header in HTTP request to load a resource file and return a Katana localized version of the title and description if the preferred language is Japanese.   Logically, seems to make sense.  Change the default language on the browser and get the localized content.  </p>
<p>Except there is a hole in the solution you can drive a bus through.  When the googlebot crawler indexes sights it doesn&#x2019;t have different crawlers for google.com versus google.co.jp versus other localized google sites.  It has a single googlebot that indexes pages and doesn&#x2019;t provide a language header.  With the change, google would not appear to have interpreted the page in any way different, which is the case because it doesn&#x2019;t get any different HTML.</p>
<p>From the Google Webmaster <a href=" http://googlewebmastercentral.blogspot.com/2010/03/working-with-multi-regional-websites.html?showComment=1268645770595#c7824467655005356998">Blog</a> we know content negotiation is bad.</p>
<blockquote><p>@Simon Content negotiation is bad because search engines will only be able to see one of the versions (and might never see the other one). Also, many users (including me) don&#x2019;t like it when they click on a search result that is not shown in the same language as their query (and the snippet) (actually, I personally don&#x2019;t just not like it, it makes me quite mad :-)).
</p></blockquote>
<h3>SEO Solution</h3>
<p>Examining that the technical delivery failed, I backed up from there on the original solution concept.  </p>
<p>The solution concept was to just add the Katana characters after the title and as a second description.  According the the following <a href="http://www.seoptimise.com/blog/2011/04/google-test-multiple-meta-descriptions-work-as-expected-social-search-does-not.html">information</a> who experimented upon other results found, as expected, that google ignores multiple meta-description tags.  It points to other article about partial success in google summaries using more relevant sentence from multi line description.</p>
<p>So the point of this is to try and see how multi languages will work in context of a description.  Will update with results.</p>
<h3>Update 1</h3>
<p>The first launch failed miserably because the theme is not setting or pulling in the meta title or description fields.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Install Your Own URL Shortener]]></title><description><![CDATA[<!--kg-card-begin: html--><p>A while back I acquired domain, zod.im with intent of setting up my own shortened URL service.  While there is nothing wrong with the current offerings by <a href="http://bit.ly/">bit.ly</a> or <a href="http://tinyurl.com/">TinyURL</a>, I was academically intrigued by the notion of setting this up.  </p>
<h2>YOURLS</h2>
<p>I went with open source package</p>]]></description><link>https://michaelleo.com/install-your-own-url-shortener/</link><guid isPermaLink="false">5ee6a02993ec950001b02665</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Fri, 04 Mar 2011 22:26:36 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>A while back I acquired domain, zod.im with intent of setting up my own shortened URL service.  While there is nothing wrong with the current offerings by <a href="http://bit.ly/">bit.ly</a> or <a href="http://tinyurl.com/">TinyURL</a>, I was academically intrigued by the notion of setting this up.  </p>
<h2>YOURLS</h2>
<p>I went with open source package <a href="http://yourls.org/">YOURLS</a>. It was pretty easy to setup and has a nice interface.  The documentation didn&#x2019;t provide me everything I needed, but what it didn&#x2019;t provide was easily discovered.  Examples: </p>
<ul>
<li>I don&#x2019;t install MySQL databases on a daily, weekly, or monthly basis, so I had to go look that back up since the commands weren&#x2019;t included directly in the setup documentation.
</li>
<li>I front nginx as the web server and chose to look up changes to nginx configuration as the yourls installer provides just content for .htaccess
</li>
<li>I use WordTwit for notifications of posts on this blog to send to Twitter.  The current version did not have yourls support, but looks like the next Pro version might have more pluggable solution.  So made updates to plugin to use this now.
</li>
</ul>
<h2> Installation </h2>
<h3> Pre-Requisites</h3>
<ul>
<li>This assumes the starting point of installation already has nginx installed with fastcgi able to interpret php.
</li>
<li>This also assumes MySQL is installed and operational.
</li>
</ul>
<h3> MySQL </h3>
<ol>
<li>Create the database </li>
<p><code><br>
mysql&gt; CREATE DATABASE yourlsdb;<br>
</code></p>
<li>Create the user &amp; Grant </li>
<p><code><br>
mysql&gt; CREATE USER &apos;yourlsdbuser&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;password&apos;;</code></p>
<p>mysql&gt; GRANT ALL PRIVILEGES ON yourlsdb.* TO yourlsdbuser@localhost;<br>

</p></ol>
<h3> Install YOURLS</h3>
<ol>
<li> Download the code </li>
<p>Check <a href="http://code.google.com/p/yourls/downloads/list">here</a> for the latest version.<br>
<code><br>
$ wget http://code.google.com/p/yourls/downloads/detail?name=yourls-1.5.zip<br>
</code></p>
<li> Unzip the code in directory you want as root </li>
<p><code><br>
$ sudo mkdir -p /var/www/zod.im/web<br>
$ cd /var/www/zod.im/web/<br>
$ unzip <path to>/yourls-1.5.zip<br>
</path></code></p>
<li> Update the configuration </li>
<p>The following is the minimum settings that need to be changed for the private linking to work.  Read through the configuration for other changes to consider.</p>
<ol>
<li> Copy the sample configuration </li>
<p><code><br>
$ cp includes/config-sample.php user/config.php<br>
</code></p>
<li>Update the database settings </li>
<p>Use vi or whatever your favorite editor is to edit the new file and follow the information in the file.<br>
Change the database settings.<br>
<code><br>
...<br>
define( &apos;YOURLS_DB_USER&apos;, &apos;dbuser&apos; );</code></p>
<p>/** MySQL database password */<br>
define( &apos;YOURLS_DB_PASS&apos;, &apos;dbpassword&apos; );</p>
<p>/** The name of the database for YOURLS */<br>
define( &apos;YOURLS_DB_NAME&apos;, &apos;yourls&apos; );</p>
<p>/** MySQL hostname */<br>
define( &apos;YOURLS_DB_HOST&apos;, &apos;localhost&apos; );<br>
...<br>
</p>
<li> Update the base site URL </li>
<p><code><br>
/** YOURLS installation URL, no trailing slash */<br>
define( &apos;YOURLS_SITE&apos;, &apos;http://example.com&apos; );<br>
</code></p>
<li>Update the cookie hash</li>
<p>As the comment shows go <a href="http://yourls.org/cookie">here</a> to get a new random hash.<br>
<code><br>
/** A random secret hash used to encrypt cookies. You don&apos;t have to remember it, make it long and complicated. Hint: copy from http://yourls.org/cookie **/<br>
define( &apos;YOURLS_COOKIEKEY&apos;, &apos;qQ4KhL_pu|s@Zm7n#%:b^{A[vhm&apos; );<br>
</code></p>
<li> Update the username and password </li>
<p>The username and passwords are in the configuration file with password in clear text. Since this isn&#x2019;t secure, I recommend not using a password that works anywhere else.<br>
<code><br>
/**  Username(s) and password(s) allowed to access the site */<br>
$yourls_user_passwords = array(<br>
        &apos;username&apos; =&gt; &apos;password&apos;,<br>
        &apos;username2&apos; =&gt; &apos;password2&apos;      // You can have one or more &apos;login&apos;=&gt;&apos;password&apos; lines<br>
        );<br>
</code>
</p></ol>
</ol>
<h3> Setup Nginx for Rewrite</h3>
<p>Edit the nginx configuration for the domain.  The configuration of YOURLS provides an .htaccess file with rewrite rules which is fine for Apache, but not so well with nginx. </p>
<p><code></code></p>
<p>server {<br>
  server_name www.zod.im www.zodim.com;<br>
  rewrite ^(.*) http://zod.im$1 permanent;<br>
}</p>
<p>server {<br>
    listen   80;<br>
    server_name zod.im;</p>
<p>    access_log /var/log/nginx/zod.im.access.log;<br>
    error_log /var/log/nginx/zod.im.error.log;</p>
<p>    root   /var/www/zod.im/web/;<br>
    index  index.php index.html;</p>
<p>    error_page    404    /404.php;</p>
<p>  location / {<br>
    # If the file exists as a static file serve it directly without<br>
    # running all the other rewite tests on it<br>
    if (-f $request_filename) {<br>
      expires max;<br>
      break;<br>
    }</p>
<p>    if (!-f $request_filename){<br>
       set $rule_0 1$rule_0;<br>
    }<br>
    if (!-d $request_filename){<br>
       set $rule_0 2$rule_0;<br>
    }<br>
    if ($rule_0 = &quot;21&quot;){<br>
       rewrite ^/.* /yourls-loader.php last;<br>
    }<br>
  }<br>
  location ~ .php$ {<br>
    set  $script     $uri;<br>
    set  $path_info  &quot;&quot;;</p>
<p>    if ($uri ~ &quot;^(.+\.php)(/.+)&quot;) {<br>
      set  $script     $1;<br>
      set  $path_info  $2;<br>
    }</p>
<p>    include /etc/nginx/fastcgi_params;<br>
    fastcgi_pass 127.0.0.1:5555;<br>
    fastcgi_index index.php;</p>
<p>    fastcgi_param SCRIPT_FILENAME /var/www/zod.im/web$fastcgi_script_name;<br>
    fastcgi_param  PATH_INFO        $path_info;<br>
    fastcgi_intercept_errors on;</p>
<p>    root /var/www/zod.im/web;<br>
  }<br>
</p>
<p>Restart nginx</p>
<h3> Go through setup </h3>
<p>Open browser and go to the admin page, login and go through initial setup process.</p>
<p>
http://example.com/admin/
</p>
<p>You get two screens to see.  First is initial welcome with button to install.  Click this and get the second telling you it has created the database tables.</p>

		<style type="text/css">
			#gallery-2 {
				margin: auto;
			}
			#gallery-2 .gallery-item {
				float: left;
				margin-top: 10px;
				text-align: center;
				width: 33%;
			}
			#gallery-2 img {
				border: 2px solid #cfcfcf;
			}
			#gallery-2 .gallery-caption {
				margin-left: 0;
			}
			/* see gallery_shortcode() in wp-includes/media.php */
		</style>
		<div id="gallery-2" class="gallery galleryid-175 gallery-columns-3 gallery-size-thumbnail"><dl class="gallery-item">
			<dt class="gallery-icon landscape">
				<a href="https://wp.michaelleo.com/2011/03/install-your-own-url-shortener/screen-shot-2011-03-04-at-10-17-54-pm/"><img width="150" height="150" src="https://michaelleo.com/content/images/wordpress/2011/03/Screen-shot-2011-03-04-at-10.17.54-PM-150x150.png" class="attachment-thumbnail size-thumbnail" alt></a>
			</dt></dl><dl class="gallery-item">
			<dt class="gallery-icon landscape">
				<a href="https://wp.michaelleo.com/2011/03/install-your-own-url-shortener/screen-shot-2011-03-04-at-10-18-28-pm/"><img width="150" height="150" src="https://michaelleo.com/content/images/wordpress/2011/03/Screen-shot-2011-03-04-at-10.18.28-PM-150x150.png" class="attachment-thumbnail size-thumbnail" alt srcset="https://michaelleo.com/content/images/wordpress/2011/03/Screen-shot-2011-03-04-at-10.18.28-PM-150x150.png 150w, https://michaelleo.com/content/images/wordpress/2011/03/Screen-shot-2011-03-04-at-10.18.28-PM-300x300.png 300w, https://michaelleo.com/content/images/wordpress/2011/03/Screen-shot-2011-03-04-at-10.18.28-PM.png 403w" sizes="(max-width: 150px) 100vw, 150px"></a>
			</dt></dl>
			<br style="clear: both">
		</div>

<p>The second screen may complain about not being able to write the .htaccess file, but since nginx is used, that can easily be ignored.</p>

<h2> Conclusion </h2>
<p>The process to create your own personal shortening service can take a little as little as 15 minutes with YOURLS.  It will probably take longer to figure out your short domain name and acquire it.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Impressions of Sony Dash]]></title><description><![CDATA[<!--kg-card-begin: html--><p>
For Christmas I received the Sony Dash as present from my wife. &#xA0;Having worked with computers, networking, and all sorts of technology in general I am both amazed and dismayed by Sony Dash at the same time.</p>
<p>Since this article can live for a long time and the Sony</p>]]></description><link>https://michaelleo.com/impressions-of-sony-dash/</link><guid isPermaLink="false">5ee6a02993ec950001b02663</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Thu, 17 Feb 2011 09:29:29 GMT</pubDate><media:content url="https://michaelleo.com/content/images/2020/09/sony-dash.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><img src="https://michaelleo.com/content/images/2020/09/sony-dash.jpg" alt="Impressions of Sony Dash"><p>
For Christmas I received the Sony Dash as present from my wife. &#xA0;Having worked with computers, networking, and all sorts of technology in general I am both amazed and dismayed by Sony Dash at the same time.</p>
<p>Since this article can live for a long time and the Sony product team does seem to making monthly/bi-monthly updates, some of these comments may become outdated. &#xA0;</p>
<h2>Amazed</h2>
<p><strong>Portal Integration</strong><br>
The device uses a web application to help install and configure the apps that run on the device.  The portal makes it relatively easy to find apps that are available, but if the number grows, it will need to address some things to make it easier to find useful ones.</p>
<p>Unfortunately, a lot of the configuration is via Flash, which makes sense given the apps on the Dash itself are built in Flash.  This makes it impossible to use with iPad.  If the Dash lives in the bedroom, it is easier to configure it with the iPad rather than laptop that usually stays downstairs.  It&#x2019;ll probably work with Android tablets, but don&#x2019;t currently have one of those.</p>
<p><strong>Netflix</strong><br>
We don&#x2019;t have a TV in our bedroom, and while the prospect of watching a movie on 7 inch screen isn&#x2019;t going to be the bedroom home theater of choice, it is still an interesting device to have Netflix access on.  It is able to access my Wireless-N WPA2 network which is good as I would like to phase out the B/G WEP.  Anyway, Netflix runs fairly well on it.  Netflix is probably the most ubiquitous service I have access to use.  </p>
<h2>Dismayed</h2>
<p><strong>Internet Radio<br>
</strong>Given that the Dash does not provide AM/FM radio built-in, this is the best app to use to listen to your local stations. &#xA0;Unfortunately,&#xA0;this looks like one heck of a unfinished app; more to the point it appears to be more proof of concept that indeed Internet Radio can be streamed and is missing the consumer oriented features that make it easy to use. &#xA0;In my world of development, this is would be a manual test harness the backend developer put together to prove the playing of audio streams works.</p>
<p>Then Internet Radio Streamer allows for user to manually add streams that can then be played. &#xA0;By manually adding streams, you have to know the URL of the stream and type it in completely and the content type of stream, m3u or pls. &#xA0; &#xA0;There is no pre-screened list of stations or search functionality. &#xA0;You can only manually add new or edit &amp; delete existing streams. &#xA0;The list of added streams can&#x2019;t even be reordered or categorized.</p>
<p>OK, so to use the app I need to use my computer to go find stream URLs and add them to the Dash. &#xA0;Not too big a deal since the device does allow adding and configuration of apps on the web site. &#xA0;I can fire up Safari, find the streams and copy/paste to the Dash configuration site. &#xA0;Does this app allow for configuration on the web site? &#xA0;No. &#xA0;So now you have to have computer within eyes view of Dash to enter the stream URL.</p>
<p>I don&#x2019;t go looking for audio streaming URLs everyday, so didn&#x2019;t know where to go. &#xA0;My first instinct was to use put in radio stations into search box, &#x201C;knx 1070 audio stream url&#x201D;. &#xA0;This had success in getting me to pages that would play the stream online, but made getting the URL difficult. &#xA0;I used Safari Web Inspector to find the URL. &#xA0;One down, many to go.</p>
<p>Eventually, I decided to go a different route. &#xA0;I eventually found some aggregator sites that provided better access to the URL. &#xA0;The main site I used for most stations I eventually added was <a href="http://vtuner.com/">vTuner</a>. &#xA0;VTuner provides an aggregation list of audio streams and an API to access the list. &#xA0;Their API has been added to many devices, just not the Sony Dash (yet?).  It seems the Internet Radio Streamer app can be made vastly more user friendly.</p>
<p><strong>Responsiveness</strong><br>
The thing is slow to respond to inputs.  I hit a button on screen and then need to wait several seconds most of the time until I can tell whether or not it recognized the input.  I understand the device is intended to be inexpensive, which probably limits what strength processor can be purchased, but wow.</p>
<h2>Parting thoughts</h2>
<p>While I have explored some apps, I haven&#x2019;t had an opportunity to fully find the ones that fully interest me.  I like the Dash and can&#x2019;t wait to see what updates come in the future to make it more useful.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[ForbesPrint.com and Open Source]]></title><description><![CDATA[<!--kg-card-begin: html--><p>A year ago, my wife, daughter and I visited my wife&#x2019;s aunt and uncle in Waterford, Michigan area.  She signed me up to do a small <a href="http://www.forbesprint.com/">web site</a> for them to promote the printing business.  The printing business has been in the family since 1946. </p>
<p>I decided to</p>]]></description><link>https://michaelleo.com/forbesprint-com-and-open-source/</link><guid isPermaLink="false">5ee6a02993ec950001b02660</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Tue, 22 Jun 2010 21:22:40 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>A year ago, my wife, daughter and I visited my wife&#x2019;s aunt and uncle in Waterford, Michigan area.  She signed me up to do a small <a href="http://www.forbesprint.com/">web site</a> for them to promote the printing business.  The printing business has been in the family since 1946. </p>
<p>I decided to use open source tools as much as possible to deliver the site.  Long term, I don&#x2019;t expect to have access to tools I use at work and I actually know I shouldn&#x2019;t use tools from work to work on external projects. And so, I stayed with open source tools as much as possible.</p>
<p>I set up the website relatively quickly with ruby on rails, but did not launch it for awhile.  While I like the ruby on rails approach to rapid development I haven&#x2019;t used ruby enough to learn the syntax and be productive in it.  In the meantime between developing the site and actually launching it, I worked on a project for a client that utilized <a href="http://www.symfony-project.org">symfony</a>.   This gave me enough opportunity and time to learn php and the symfony framework.  So the in-development site was migrated from a ruby on rails to symfony project.  </p>
<p>One thing I liked when I set up the rails project was the usage of <a href="http://www.capify.org">capistrano</a> to manage the deployment of the application to my slicehost instance.  I decided to see how it could work with symfony as well.  In the end, I was able to get a good amount of information from the following <a href="http://blog.codingspree.net/2008/5/12/deploying-symfony-project-with-capistrano/">site</a>.  Besides paths and server changes to the configuration presented on the page, I utilize git as version control.   </p>
<p>I have found great value in git over subversion.  I like the fact that git does not require server software to run.  Getting it <a href="http://articles.slicehost.com/2009/5/13/capistrano-series-setting-up-git">up and running</a> with just a dedicated user and ssh was a snap.  Even better, git doesn&#x2019;t manage every file with 3 extra files per instance like subversion seems to do.  To copy a subversion branch from one server to another takes forever due to all the .svn files.  Git can easily be moved around and ability to remove git from a file structure works just as easily.  </p>
<p>In the end, I built the very small site, <a href="http://www.forbesprint.com/">ForbesPrint.com</a> with the following tools:</p>
<ul>
<li>php for scripting language</li>
<li>symfony for php framework</li>
<li>nginx for web server</li>
<li>fastcgi for php runtime environment</li>
<li>git for source control</li>
<li>gimp for image editing</li>
<li>slicehost for server hosting utilizing Ubuntu virtual machine</li>
<li>SpringSource Tool Set for IDE with Aptana Studio plugin for php project support</li>
</ul>
<p>Now that the basics are in place, I hope to expand the site to better accommodate their needs and growth the business.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Blogging on the iPad]]></title><description><![CDATA[<!--kg-card-begin: html--><p>My new 3g iPad arrived today and I have spent the evening having a look through the app store for apps that are built for the device.  One of the apps I found was WordPress for the iPad.  This is my first post on the iPad and the initial impression</p>]]></description><link>https://michaelleo.com/blogging-on-the-ipad/</link><guid isPermaLink="false">5ee6a02993ec950001b0265d</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Fri, 30 Apr 2010 21:31:13 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>My new 3g iPad arrived today and I have spent the evening having a look through the app store for apps that are built for the device.  One of the apps I found was WordPress for the iPad.  This is my first post on the iPad and the initial impression is that it is easy to do, but it is prone to moving the cursor around a little too easily.</p>
<p>So far the  other apps I have tried are Netflix and USAToday.  As my previous post mentioned, I imagine the most use of this device will be reading in certain room.  I&#x2019;ll have to wait until later to see how that goes.</p>
<p>On a technology front, my latest project involves the use of SDL Tridion for CMS.  I don&#x2019;t have too much to state about the product as yet.  I&#x2019;ll wait until the developers get going with the implementation before I mention anything about it.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[MAMP & PECL/UUID Module Working On Snow Leopard ]]></title><description><![CDATA[<!--kg-card-begin: html--><p>I had a previous article that discussed in detail on how to get <a href="http://unrealexpectations.com/blog/2010/01/mamp-imagick-on-snow-leopard/">imagick</a> php plugin working on MAMP.   As always, as you get more exposed to technologies and frameworks you find new ways to do things.  One thing to keep in mind with MAMP is that 1.8.4</p>]]></description><link>https://michaelleo.com/mamp-pecluuid-module-working-on-snow-leopard/</link><guid isPermaLink="false">5ee6a02993ec950001b0265c</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Sun, 11 Apr 2010 23:33:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>I had a previous article that discussed in detail on how to get <a href="http://unrealexpectations.com/blog/2010/01/mamp-imagick-on-snow-leopard/">imagick</a> php plugin working on MAMP.   As always, as you get more exposed to technologies and frameworks you find new ways to do things.  One thing to keep in mind with MAMP is that 1.8.4 is still 32-bit build.  Once it goes 64-bit, I am sure the whole thing will get easier.</p>
<p>In this instance, I was working through adding UUID generation support to my PHP code.  While the <a href="http://php.net/manual/en/function.uniqid.php">uniqid</a> provides a function to generate IDs, it is not a <a href="http://en.wikipedia.org/wiki/Globally_Unique_Identifier">GUID specification</a>.  There is <a href="http://pecl.php.net/package/uuid">PECL UUID module</a> that provides &#x201C;a wrapper around libuuid from the ext2utils project&#x201D;.  After looking around for how to add it to MAMP, I came across the following <a href="http://smbjorklund.no/how-enable-pecl-uploadprogress-extention-mamp#SL">article</a> that talked about how to enable the uploadprogress module.  utilizing the same process I attempted to build the uuid module.  Unfortunately, the uuid module failed to compile.  This eventually lead to a <a href="http://pecl.php.net/bugs/bug.php?id=14503">defect report</a> for the uuid module about Mac OS X and older version of files.  Attached was a patch file; patched the files and remade.  Installed, reloaded and all was good.</p>
<p>The following are the steps I used for the installation of uuid module.</p>
<p><b> Download and extract the uuid module</b><br>
Grabbed the 1.2 version of the uuid module.<br>
<code><br>
macbook:Downloads mleo$ wget http://pecl.php.net/get/uuid-1.0.2.tgz<br>
...<br>
mabook:Downloads mleo$ tar -xzf uuid-1.0.2.tgz<br>
macbook:Downloads mleo$ cd uuid-1.0.2<br>
</code></p>
<p><b> Get &amp; Apply Patch </b><br>
Copy patch code from <a href="http://pastie.org/435461">here</a>.   Save the contents into mac.patch file.  Apply patch.  It did require entry of the path to two test files during the patch process.<br>
<code><br>
macbook:uuid-1.0.2 mleo$ patch &lt; mac.patch 
patching file config.m4
patching file php_uuid.h
can&apos;t find file to patch at input line 103
Perhaps you should have used the -p or --strip option?
The text leading up to this was:
--------------------------
|diff -urp uuid-1.0.2/tests/uuid_mac.phpt uuid-
|1.0.2.mine/tests/uuid_mac.phpt
|--- tests/uuid_mac.phpt	2008-04-01 08:59:22.000000000 -0700
|+++ tests/uuid_mac.phpt	2008-08-14 10:21:57.000000000 -0700
--------------------------
File to patch: tests/uuid_mac.phpt
patching file tests/uuid_mac.phpt
can&apos;t find file to patch at input line 116
Perhaps you should have used the -p or --strip option?
The text leading up to this was:
--------------------------
|diff -urp uuid-1.0.2/tests/uuid_time.phpt uuid-
|1.0.2.mine/tests/uuid_time.phpt
|--- tests/uuid_time.phpt	2008-04-01 08:59:22.000000000 -0700
|+++ tests/uuid_time.phpt	2008-08-14 10:22:50.000000000 -0700 
--------------------------
File to patch: tests/uuid_time.phpt
patching file tests/uuid_time.phpt
patching file uuid.c
</code></p>
<p><b> Configure and Build the Module </b><br>
I&apos;ll leave the prompts out of this one to make it easier to copy and paste the set of commands, but each line in the below code block is different command. </p>
<p><code><br>
/Applications/MAMP/bin/php5/bin/phpize<br>
MACOSX_DEPLOYMENT_TARGET=10.6<br>
CFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp&quot;<br>
CCFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot;<br>
CXXFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot;<br>
LDFLAGS=&quot;-arch i386 -arch x86_64 -bind_at_load&quot;<br>
export CFLAGS CXXFLAGS LDFLAGS CCFLAGS MACOSX_DEPLOYMENT_TARGET<br>
./configure --with-php-config=/Applications/MAMP/bin/php5/bin/php-config<br>
make<br>
make install<br>
</code></p>
<p><b> Add module to php.ini </b><br>
Edit the /Applications/MAMP/conf/php5/php.ini file and add &quot;extension=uuid.so&quot;.</p>
<p><b> Test Installation </b><br>
Can check info to see if uuid module loads and run the uuid_create() function to see if it returns.</p>
<p><code><br>
macbook:uuid-1.0.2 mleo$ /Applications/MAMP/bin/php5/bin/php -r &apos;print phpinfo();&apos;<br>
...<br>
uuid<br>
UUID extension</code></p>
<p>Version =&gt; 1.0.2 (stable)<br>
Released =&gt; 2008-04-01<br>
CVS Revision =&gt; $Id: uuid.c,v 1.9 2008/04/01 15:58:52 hholzgra Exp $<br>
Authors =&gt; Hartmut Holzgraefe &apos;hartmut@php.net&apos; (lead)<br>
...</p>
<p>macbook:uuid-1.0.2 mleo$ /Applications/MAMP/bin/php5/bin/php -r &apos;print uuid_create() . &quot;\n&quot;;&apos;<br>
85C43B35-1416-435F-AEFF-3E4693ACEE65<br>
macbook:uuid-1.0.2 mleo$ </p>
<p></p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Most Popular Room for iPad]]></title><description><![CDATA[<!--kg-card-begin: html--><p>My prediction for the iPad is that some survey will come out 6 months after the release that finds people use their iPad more often in the bathroom than any other room in the house.  The logic on this is simple.  People already have laptops, desktops and fully functional computers</p>]]></description><link>https://michaelleo.com/most-popular-room-for-ipad/</link><guid isPermaLink="false">5ee6a02993ec950001b0265a</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Wed, 31 Mar 2010 21:46:11 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>My prediction for the iPad is that some survey will come out 6 months after the release that finds people use their iPad more often in the bathroom than any other room in the house.  The logic on this is simple.  People already have laptops, desktops and fully functional computers available in the living room, office and/or bedroom.  The bathroom is really one of last bastions of truly tech free peace and quiet.  </p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Using YAML as Response Format for Services in Spring]]></title><description><![CDATA[<!--kg-card-begin: html--><p>Recently, we were developing an iPhone application where the iPhone developer was insistent on utilizing <a href="http://www.yaml.org/">YAML</a> as the response format for service calls.  This seemed odd given the prevalence of JSON as a response format and an already built <a href="http://code.google.com/p/json-framework/">library</a> for parsing and handling JSON within Objective C that works</p>]]></description><link>https://michaelleo.com/using-yaml-as-response-format-for-services-in-spring/</link><guid isPermaLink="false">5ee6a02993ec950001b02658</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Tue, 02 Mar 2010 11:26:20 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>Recently, we were developing an iPhone application where the iPhone developer was insistent on utilizing <a href="http://www.yaml.org/">YAML</a> as the response format for service calls.  This seemed odd given the prevalence of JSON as a response format and an already built <a href="http://code.google.com/p/json-framework/">library</a> for parsing and handling JSON within Objective C that works with the iPhone SDK.  The other advantage to JSON is there is another <a href="http://spring-json.sourceforge.net/">project</a> that has built a JSON view resolver for converting model/domain objects into JSON.   The advantage with YAML is that it inherently supports circular references and can resolve them during serialization and deserialization.  While domain objects can have circular references in design, I try to keep them minimal and out of the model objects to ensure most serialization techniques work easily.</p>
<p>I couldn&#x2019;t quickly find any YAML Spring View support so went about working through my own.  Using the source of the JSON Spring View project as baseline I was quickly able to build out a <em>baseline</em> view handler.  I say <em>baseline</em> because I haven&#x2019;t fully unit tested the code as yet.  </p>
<p><strong>YamlView Class</strong><br>
This makes use of the <a href="http://jyaml.sourceforge.net/">jyaml</a> library. </p>
<p><code><br>
public class YamlView extends AbstractView {<br>
    private static final String DEFAULT_YAML_CONTENT_TYPE = &quot;application/yaml&quot;;</code></p>
<p>    public YamlView() {<br>
        super();<br>
        setContentType(DEFAULT_YAML_CONTENT_TYPE);<br>
    }</p>
<p>    /**<br>
     * Creates a YAML String from the model values.<br>
     */<br>
    @SuppressWarnings(&quot;unchecked&quot;)<br>
    protected final String defaultCreateYaml(Map model) {<br>
        return Yaml.dump(model);<br>
    }</p>
<p>    /**<br>
     * Creates a Yaml String from the model values.<br>
     */<br>
    @SuppressWarnings(&quot;unchecked&quot;)<br>
    protected String createYaml(Map model, HttpServletRequest request, HttpServletResponse response) {<br>
        return defaultCreateYaml(model);<br>
    }</p>
<p>    @SuppressWarnings(&quot;unchecked&quot;)<br>
    @Override<br>
    protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)<br>
            throws Exception {</p>
<p>        response.setContentType(getContentType());<br>
        writeYaml(model, request, response);<br>
    }</p>
<p>    @SuppressWarnings(&quot;unchecked&quot;)<br>
    protected void writeYaml(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {</p>
<p>        String yaml = createYaml(model, request, response);<br>
        response.getWriter().write(yaml);</p>
<p>    }<br>
}<br>
</p>
<p><strong>Configuration: applicationContext.xml</strong><br>
In one of the Spring configuration files where the web presentation layer is set.<br>
<code><br>
...<br>
	<bean name="xmlViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver" p:order="3" p:location="classpath:xmlViews.xml"><br>
...<br>
</bean></code><br>
The order is in place because this configuration file has multiple ViewResolvers.  The xmlViews.xml then has the configuration:<br>
<code><br>
<?xml version="1.0" encoding="UTF-8"?><br>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"></beans></code></p>
<p>    <bean name="jsonView" class="org.springframework.web.servlet.view.json.JsonView"></bean></p>
<p>    <bean name="yamlView" class="com.example.web.view.YamlView"></bean></p>
<p><br>
</p>
<p>And finally, the usage within a controller:</p>
<p><code><br>
public class ExampleController extends AbstractCommandController {<br>
...<br>
	@Override<br>
	protected ModelAndView handle(HttpServletRequest request,<br>
			HttpServletResponse response, Object command, BindException bindException)<br>
			throws Exception {</code></p>
<p>...<br>
		String viewType = &quot;jsonView&quot;;<br>
		if (request.getRequestURI().endsWith(&quot;.yaml&quot;)) {<br>
			viewType = &quot;yamlView&quot;;<br>
		}</p>
<p>		return new ModelAndView(viewType, model);<br>
	}<br>
...<br>
}</p>
<p></p>
<p>It really is amazing the little amount of custom code required these days to get great results.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Gadgets Galore - The iPad would just be a small incemental add]]></title><description><![CDATA[<!--kg-card-begin: html--><p>The Apple iPad was announced yesterday and I do believe the hype was so insane leading up to the announcement that Steve Jobs could have introduced a semi-transparent 1/4 inch laser screen that ran for a month on full charge and people would still have made fun of the</p>]]></description><link>https://michaelleo.com/gadgets-galore-the-ipad-would-just-be-a-small-incemental-add/</link><guid isPermaLink="false">5ee6a02993ec950001b02657</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Fri, 29 Jan 2010 00:59:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>The Apple iPad was announced yesterday and I do believe the hype was so insane leading up to the announcement that Steve Jobs could have introduced a semi-transparent 1/4 inch laser screen that ran for a month on full charge and people would still have made fun of the name.  I kind of think the first generation of the iPad should just have been called the iPod Touch XL as it more like the Touch and less like the iPhone.  </p>
<p>The day after led to all the news and I came across an interesting <a href="http://news.yahoo.com/s/ap/20100128/ap_on_hi_te/us_fea_lifestyles_gadget_overload">AP article</a> that stated</p>
<blockquote><p>
&#x2026; the average household owns about 24 electronic gadgets &#x2026;
</p></blockquote>
<p>and this got me thinking about how many we own in our household of 3 people.   I came up with the following list of over 40 items that are used on regular or semi-regular basis and are likely plugged-in sucking power.  </p>
<h2>The List</h2>
<p>I&#x2019;ll list the items in the home theater and summarize the others.</p>
<p><strong>Home Theater: 10 Gadgets</strong></p>
<ol>
<li>Sony Receiver  &amp; Bose Surround Sound Speakers (2001)</li>
<li>Sony Plasma TV &#x2013;Living Room (2003)</li>
<li>Sony DVD player (2004)</li>
<li>TiVo HD* (2007)</li>
<li>Sony PS3* (2006)</li>
<li>Apple AppleTV* (2007)</li>
<li>Actiontec Cable/Moca Modem* (2007)</li>
<li>Linksys Simultaneous Dual Band Router* (2009)</li>
<li>Western Digital NAS* (2008)</li>
<li>Logitech Universal Remote (2008)</li>
</ol>
<p>Arguably, the receiver and speakers are 2 items, but the one is really not useful without the other in this case.</p>
<p><strong>Audio Players: 4 Gadgets</strong><br>
We currently have 3 working iPods (Video, Mini, Shuffle) not counting the iPhone.  The Mini and Shuffle aren&#x2019;t used much anymore.  The Video is hooked up to the screen in the car to provide hours of Handy Manny to my daughter on long car trips.  We also have a Delphi Portable XM Satellite receiver that my wife uses at work.</p>
<p><strong>Cameras: 5 Gadgets</strong><br>
We have 5MB and 8MB point and shoot digital cameras.  I received a Canon 20D DSLR for Christmas in 2005 and added an eyeFi Wireless SD card (with CF adapter) a couple years later.  We added an HDD Digital Camcorder before my daughter was born.  </p>
<p><strong>Kitchen: 4 Gadgets</strong><br>
In the kitchen is set top box hooked up to TV with a Mac Mini connected to network via a Netgear MOCA bridge.</p>
<p><strong>Bedrooms:  7 Gadgets</strong><br>
We have 2 bedrooms but have kept to keep one of them free of television, and at some point will probably remove the television from the second one as well.  But currently across the two bedrooms we have Desktop computer &amp; printer, set top box, TV, and Slingbox.  To have the computer and slingbox connected to the network I use Linksys Dual Band Ethernet Wireless Bridge and Slink Ethernet over Power bridge.  </p>
<p><strong>Other Entertainment: 4 Gadgets</strong><br>
I purchased an Amazon Kindle for my wife this past Christmas.  The Kindle is amazingly better in functionality than the Sony eReader purchased 2 years ago.   The eReader isn&#x2019;t used anymore and isn&#x2019;t in this list.  The other items include 2 Apple Airport Expresses purchased about 3 years apart.  One was for the home theater long before the AppleTV was added and the other was for bedroom and printer.  Finally, we have a Brookstone wireless speaker that is more often hooked up to one of the Airports now. </p>
<p><strong>Portable Game Stations: 2 Gadgets</strong><br>
A Sony PSP and Nintendo DS.</p>
<p><strong>Phones: 3 Gadgets</strong><br>
We still have regular landlines and 2 smart phones, an Apple iPhone 3G and Samsung Blackjack II.</p>
<p><strong>Work Related: 4 Gadgets</strong><br>
My wife and I each have a laptop provided by our employers and so maybe these not count towards the list.  I also have a Linksys Travel Router and the all important iGo power adapter with 8 or so different tips; something has to be able to provide power to all the devices.</p>
<p><strong>Cars: 2+ Gadgets</strong><br>
Add to this our two cars that both have built-in GPS and one that has a DVD player with back seat screen and XM satellite radio.</p>
<p>So based on this number, adding an iPad to the household is only a 2% increase in the number of gadgetry we have to deal with.  </p>
<h2> Connected Devices </h2>
<p>Going through that list of gadgets and counting the number of items that can connect to the local network or Internet via Ethernet, MOCA, or 802.11 came to 19 devices.  Having so many devices is part of why I have tried to upgrade and replace network devices more than anything else.  The Ethernet over Powerline worked for the Slingbox in one bedroom but not the desktop in another room.  The desktop got the Dual Band Ethernet Bridge and this performs pretty well.  The Mac Mini was also using wireless and wanted to see if I could do better.  I have Verizon FIOS and they use MOCA for the set top boxes to connect to the network and receive programming guide data as well as Video On Demand.  I started looking at MOCA bridges and added a Netgear device for the Mac Mini to connect to the router.  So I have pretty much tried every major consumer based physical medium for local area networks.</p>
<h2>Replacing Gadgets</h2>
<p>Most of the items in this list were purchased over the course of 10 years so it&#x2019;s not like we went hog wild in a short period of time.  I have generally been lucky with electronics and have had them run as long as needed.  The only exception seems to be wireless routers, Logitech remotes, and of course mobile phones.  Over the course of the 10 years I had a wired router, 1 wireless B, 3 wireless G, and 2 wireless N routers.  The switch from B to G to N to N (simultaneous dual band) was more about speed increases than failure.   The Logitech remotes on the other hand&#x2026;  I think I had 3 that hit the wood floor and failed.  The mobile phone changes consisted of 9 phones over 12 years.  4 changes were due to service provider changes.  3 were due to failure or loss.  Only 2 were explicitly for something new.  Switching to AT&amp;T to get the iPhone was for something new.</p>
<h2>Unplugged, but Serviceable</h2>
<p>I won&#x2019;t get into the unplugged devices, mostly it is older gaming systems.  </p>
<h2>Wrapping It Up</h2>
<p>So, do I think I will get an iPad.  Maybe, but not soon after it is released.  I really think it is no more than extra large iPod Touch in this first generation presented.  However, it has potential to open floodgates of creativity in further development of apps.  Additionally, Apple has yet to release features for iPhone OS 4.0 and I imagine it will provide more interesting features for the next iPhone and updates to the iPad.</p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[One Line Command - Print Directory Tree]]></title><description><![CDATA[<!--kg-card-begin: html--><p>While this blog is new, I want to ensure I keep content going lest it goes abandoned and six months from now I wonder what happened.  Most of my original inspiration on putting content here is based on useful work I do.  Not having anything interesting to share from current</p>]]></description><link>https://michaelleo.com/one-line-command-print-directory-tree/</link><guid isPermaLink="false">5ee6a02993ec950001b02656</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Tue, 26 Jan 2010 16:56:45 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>While this blog is new, I want to ensure I keep content going lest it goes abandoned and six months from now I wonder what happened.  Most of my original inspiration on putting content here is based on useful work I do.  Not having anything interesting to share from current work, I&#x2019;ll share a segment of things I find interesting and useful from time to time.</p>
<p>So this post is kicking off my category of &#x201C;One Line Commands&#x201D;.  Over the years I have found myself finding, grepping, cutting and PERLing through directories and files to find some useful information to solve some issue.  This first example is a PERL script that recurses through directory tree printing and adds indent based on the current level.  Output can be sent to a file and imported into Excel.  It is setup to ignore subversion files/directories because at the time I wanted to get a full directory tree from a checked out repository.</p>
<p><code><br>
perl -e &apos;use File::Find; sub pl {my ($sc) = @_; print &quot;\t&quot; while ($sc--<br>
&gt; 0); } sub wanted { $f=$_; $fn=$File::Find::name; return if $fn =~ /.svn/; my<br>
$sc=($fn =~tr/\///); if (-d $fn) {pl($sc); print &quot;$f/\n&quot;;} else {pl($sc); print<br>
 &quot;$f&quot;; pl(20-$sc); print &quot;$fn\n&quot;;}};  find(\&amp;wanted, (&quot;.&quot;));&apos;<br>
</code></p>
<p>Copy and pastes into one command line entry. </p>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[MAMP & Imagick on Snow Leopard]]></title><description><![CDATA[<!--kg-card-begin: html--><p>Most web searches for adding the imagick extension to <a href="http://www.mamp.info/en/index.html">MAMP</a> suggest using <a href="http://www.macports.org/">MacPorts</a> to install ImageMagick and then use this compiled version as the library to running &#x2018;pecl install imagick&#x2019;.</p>
<p>As I was in the process of setting up a clean Mac Book Pro, I tried going through</p>]]></description><link>https://michaelleo.com/mamp-imagick-on-snow-leopard/</link><guid isPermaLink="false">5ee6a02993ec950001b02655</guid><dc:creator><![CDATA[Michael Leo]]></dc:creator><pubDate>Tue, 05 Jan 2010 16:51:20 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: html--><p>Most web searches for adding the imagick extension to <a href="http://www.mamp.info/en/index.html">MAMP</a> suggest using <a href="http://www.macports.org/">MacPorts</a> to install ImageMagick and then use this compiled version as the library to running &#x2018;pecl install imagick&#x2019;.</p>
<p>As I was in the process of setting up a clean Mac Book Pro, I tried going through this process.   I quickly ran into some problems getting the imagick extension running under MAMP.  This is a tail of that adventure.</p>
<p>I started with the latest version of MAMP 1.8.4 and MacPorts 1.8.2 installed.<br>
Then installed ImageMagick</p>
<p><code><br>
$ sudo port install ImageMagick<br>
</code></p>
<p>This went off without a problem.  Could then easily run the &#x2018;convert&#x2019; program to see if is working.  Next step felt like to install imagick extension.</p>
<p><code><br>
$ cd /Applications/MAMP/bin/php5/bin<br>
</code></p>
<p>And just make sure the right version of PECL is executed  via &#x2018;./&#x2019; else may get Mac OS X installed or even MacPorts installed version.</p>
<p><code><br>
$ ./pecl install imagick<br>
</code></p>
<p>The process doesn&#x2019;t stop with error, but immediately it has gripes about missing header files.</p>
<p><code><br>
downloading imagick-2.3.0.tgz ...<br>
Starting to download imagick-2.3.0.tgz (86,976 bytes)<br>
.....................done: 86,976 bytes<br>
12 source files, building<br>
WARNING: php_bin /Applications/MAMP/bin/php5/bin/php appears to have a suffix 5/bin/php, but config variable php_suffix does not match<br>
running: phpize</code></p>
<p>grep: /Applications/ MAMP/bin/php5/include/php/main/php.h: No such file or directory</p>
<p>grep: /Applications/ MAMP/bin/php5/include/php/Zend/zend_modules.h: No such file or directory</p>
<p>grep: /Applications/ MAMP/bin/php5/include/php/Zend/zend_extensions.h: No such file or directory</p>
<p>Configuring for:<br>
PHP Api Version:<br>
Zend Module Api No:<br>
Zend Extension Api No:<br>
configure.in:158: warning: AC_CACHE_VAL(lt_prog_compiler_static_works, ...): suspicious cache-id, must contain _cv_ to be cached<br>
../../lib/autoconf/general.m4:1998: AC_CACHE_VAL is expanded from...<br>
...<br>
aclocal.m4:4641: _LT_AC_TAGCONFIG is expanded from...<br>
Please provide the prefix of Imagemagick installation [autodetect] :<br>
$<br>
</p>
<p>The first problem encountered was simply getting &#x2018;pecl&#x2019; to compile the imagick extension as the MAMP distribution does not include the php headers where pecl is expecting them.</p>
<p>There is probably a way to get pecl to pick them up properly, but in the interest of time, one can just create a link from</p>
<p><code><br>
$ cd /Applications/MAMP/bin/php5</code></p>
<p>$ ln -s /Applications/MAMP/Library/include include</p>
<p><br>
And then re-run &#x2018;pecl&#x2019;</p>
<p><code><br>
$ ./pecl install imagick<br>
...<br>
Please provide the prefix of Imagemagick installation [autodetect] : /opt/local<br>
building in /var/tmp/pear-build-mleo/imagick-2.3.0<br>
running: /private/tmp/pear/temp/imagick/configure --with-imagick=/opt/local<br>
...<br>
</code></p>
<p>and provide &#x2018;/opt/local&#x2019; when prompted for prefix of Imagemagick installation.  Everthing seemed to go well.  Finally, modified /Applications/MAMP/conf/php5/php.ini to add</p>
<p><code><br>
extenstion=imagick.so<br>
</code></p>
<p>And modified /Applications/MAMP/Library/bin/envvars to add</p>
<p><code><br>
DYLD_LIBRARY_PATH=&quot;/Applications/MAMP/Library/lib:/opt/local/lib:$DYLD_LIBRARY_PATH&quot;<br>
</code></p>
<p>Finally ran:</p>
<p><code><br>
$ /Applications/MAMP/bin/php5/bin/php -i | less<br>
</code></p>
<p>to see if it was enabled.  Unfortunately, the imagick module was not listed.  After a little investigation, in the php error log was the following entry:</p>
<p><code><br>
PHP Warning:  PHP Startup: Unable to load dynamic library &apos;/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20060613/imagick.so&apos; - (null) in Unknown on line 0<br>
</code></p>
<p>After some various searching and other, checking what I did wrong, I finally considered the files:</p>
<p><code><br>
$ file /Applications/MAMP/bin/php5/bin/php<br>
/Applications/MAMP/bin/php5/bin/php: Mach-O universal binary with 2 architectures<br>
/Applications/MAMP/bin/php5/bin/php (for architecture ppc):	Mach-O executable ppc<br>
/Applications/MAMP/bin/php5/bin/php (for architecture i386):	Mach-O executable i386</code></p>
<p><code> </code></p>
<p><code>$ file /Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20060613/imagick.so<br>
/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20060613/imagick.so (for architecture x86_64):	Mach-O 64-bit x86_64<br>
</code></p>
<p>So discovered that MAMP is a Universal binary, just not an x86_64 universal binary.  So, first, make sure to rebuild imagick.so with i386 binary support and just to be safe, add those settings for the php header files as well.</p>
<p>So, back to the php distribution and run configure setting some flags, although this may not be needed, it can&#x2019;t hurt.<br>
<code><br>
$ MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp&quot; CCFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot; CXXFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot; LDFLAGS=&quot;-arch i386 -arch x86_64 -bind_at_load&quot;  ./configure --enable-shared</code></p>
<p><code> </code></p>
<p><code>loading cache ./config.cache<br>
checking for Cygwin environment... (cached) no<br>
...<br>
</code></p>
<p>Copy the headers back to MAMP location.  And re-run pecl to build universal binary.</p>
<p><code><br>
$ ./pecl uninstall imagick</code></p>
<p><code> </code></p>
<p><code>$ MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp&quot; CCFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot; CXXFLAGS=&quot;-arch i386 -arch x86_64 -g -Os -pipe&quot; LDFLAGS=&quot;-arch i386 -arch x86_64 -bind_at_load&quot;  ./pecl install imagick<br>
</code></p>
<p>Run php printinfo and<br>
<code><br>
$ /Applications/MAMP/bin/php5/bin/php -i | less</code></p>
<p><code> </code></p>
<p><code>dyld: NSLinkModule() error<br>
dyld: Library not loaded: /opt/local/lib/libMagickWand.2.dylib<br>
Referenced from: /Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20060613/imagick.so<br>
Reason: no suitable image found.  Did find:<br>
/opt/local/lib/libMagickWand.2.dylib: mach-o, but wrong architecture<br>
</code></p>
<p>And discover that MacPorts isn&#x2019;t creating universal binaries.<br>
<code><br>
$ file /opt/local/lib/libMagickCore.2.dylib<br>
/opt/local/lib/libMagickCore.2.dylib: Mach-O 64-bit dynamically linked shared library x86_64<br>
</code></p>
<p>Again, head to the favorite search engine and look at how MacPorts can work with Universal binaries.  After some criteria changes came across this simple set of <a href="http://rcaguilar.wordpress.com/2009/11/04/universal-binaries-in-macports/">details</a>.</p>
<p>MacPorts provides a variant +universal for all installs that use configure script to initialize build process that will build Universal binaries.  The specific architecture has some defaults based on the OS and current hardware.</p>
<p>A bit of pain to add +universal to every port install command, so make +universal, uh, universal it can be added to the /opt/local/etc/macports/variants.conf file as last line:</p>
<p><code><br>
# To specify global variants to use for all port builds,<br>
# customize this file to list variant settings you want.<br>
#<br>
# Be sure to uncomment/define the variants_conf setting<br>
# in the system wide ${prefix}/etc/macports/macports.conf<br>
# file or in your personal ~/.macports/macports.conf to<br>
# point to this file to enable this feature.<br>
#<br>
# Any variants specified here that are not supported by<br>
# a port will just be ignored. Multiple variants can be<br>
# specified per line, or one per line is also allowed.<br>
#<br>
# Example:<br>
# +ipv6 +no_x11<br>
+universal<br>
</code></p>
<p>And to force a rebuild of anything you have installed</p>
<p><code><br>
sudo port upgrade --force installed<br>
</code></p>
<p>Finally, we try php again:</p>
<p><code><br>
$ /Applications/MAMP/bin/php5/bin/php -i | less</code></p>
<p><code>...<br>
imagick</code></p>
<p>imagick module =&gt; enabled<br>
imagick module version =&gt; 2.3.0<br>
imagick classes =&gt; Imagick, ImagickDraw, ImagickPixel, ImagickPixelIterator<br>
ImageMagick version =&gt; ImageMagick 6.5.8-0 2010-01-05 Q16 http://www.imagemagick.org<br>
ImageMagick copyright =&gt; Copyright (C) 1999-2009 ImageMagick Studio LLC<br>
ImageMagick release date =&gt; 2010-01-05<br>
ImageMagick Number of supported formats:  =&gt; 194<br>
ImageMagick Supported formats =&gt; 3FR, A, AI, ART, ARW, AVI, AVS, B, BGR, BMP, BMP2, BMP3, BRF, BRG, C, CAL, CALS, CAPTION, CIN, CIP, CLIP, CMYK, CMYKA, CR2, CRW, CUR, CUT, DCM, DCR, DCX, DDS, DFONT, DNG, DOT, DPX, EPDF, EPI, EPS, EPS2, EPS3, EPSF, EPSI, EPT, EPT2, EPT3, ERF, FAX, FITS, FRACTAL, FTS, G, G3, GBR, GIF, GIF87, GRADIENT, GRAY, GRB, GROUP4, HALD, HISTOGRAM, HRZ, HTM, HTML, ICB, ICO, ICON, INFO, INLINE, IPL, ISOBRL, JNG, JPEG, JPG, K, K25, KDC, LABEL, M, M2V, M4V, MAP, MAT, MATTE, MIFF, MNG, MONO, MOV, MP4, MPC, MPEG, MPG, MRW, MSL, MSVG, MTV, MVG, NEF, NULL, O, ORF, OTB, OTF, PAL, PALM, PAM, PATTERN, PBM, PCD, PCDS, PCL, PCT, PCX, PDB, PDF, PDFA, PEF, PFA, PFB, PFM, PGM, PICON, PICT, PIX, PJPEG, PLASMA, PNG, PNG24, PNG32, PNG8, PNM, PPM, PREVIEW, PS, PS2, PS3, PSD, PTIF, PWP, R, RADIAL-GRADIENT, RAF, RAS, RBG, RGB, RGBA, RGBO, RLA, RLE, SCR, SCT, SFW, SGI, SHTML, SR2, SRF, STEGANO, SUN, SVG, SVGZ, TEXT, TGA, THUMBNAIL, TIFF, TIFF64, TILE, TIM, TTC, TTF, TXT, UBRL, UIL, UYVY, VDA, VICAR, VID, VIFF, VST, WBMP, WMV, WPG, X, X3F, XBM, XC, XCF, XPM, XPS, XV, XWD, Y, YCbCr, YCbCrA, YUV</p>
<p>Directive =&gt; Local Value =&gt; Master Value<br>
imagick.locale_fix =&gt; 0 =&gt; 0<br>
imagick.progress_monitor =&gt; 0 =&gt; 0</p>
<p></p>
<p><code>...<br>
</code></p>
<p>Somewhere during this adventure I definitely considered dumping MAMP and using apache, php and mysql from MacPorts but MAMP does generally provide a simple package installation and a GUI for quickly starting and stopping the services.</p>
<!--kg-card-end: html-->]]></content:encoded></item></channel></rss>