• Dipping my toe in the world of Docker

    A former co-worker of mine has talked about Docker for years and I've taken a look at it a few times, but have generally been uninterested in it. Recently with my interest in Home Assistant, I've decided to take another look as many of the installs of Home Assistant as well as Hass.io are based on Docker.

    I've used virtual machines running on VMware Fusion for years with some Windows installs and some Linux installs. I'm very comfortable with Linux, but kind of dislike maintaining different packages. There are package managers that handle much of it for me, but then there are other packages that have special installations.

    I had a few goals in mind for seeing if Docker could replace the current virtual machines I had running for Pi-hole and Observium. The goals were pretty simple that I wanted easy updates and be able to easily backup the data. In the Docker world, updates are dead simple and in many docker containers, the data is stored outside of the container making it easy to backup. As another goal, I wanted to be able to experiment with other containers to see what else I could add to my network.

    With all this in mind, I started looking at how to setup Docker. Pretty quickly, I realized that Docker for the Mac was virtually useless for me as it didn't handle all the networking that Docker running on Linux could. So that meant installing Docker on a Linux VM; that almost negated my goal of easy updates as I'd still have to update the virtual machine running Ubuntu. I could live with that if the rest of the setup was straight forward and didn't have to remember how to update each container individually.

    In order to make backups easy, I wanted to store the data on my Mac and not inside of the virtual machine. I've not had great luck with the VMWare tools for mounting volumes, so I decided to use CIFS (SMB) to mount a volume in Linux which works well except for the MariaDB (MySQL fork) Docker container. Not a big deal, I'd just add a cron job to dump the databases every few hours and store the dumps on the mounted volume. I added the following to /etc/fstab

        //myserver/account/Documents/Ubuntu /mnt/mediacenter cifs username=account,domain=WORKGROUP,password=password,rw,hard,uid=1000,gid=1000 0 0
    

    I also had to turn on Windows File Sharing options on the Mac.

    Windows File Sharing

    The crontab is:

        30 */2 * * * /usr/local/bin/backup_mysql
    

    with the backup_mysql file being

        #!/bin/sh
        /usr/bin/mysqldump -h 127.0.0.1 -u root -ppassword --lock-all-tables --all-databases | gzip > /mnt/mediacenter/backups/mysql/mysql_backup_$(date +"%m-%d-%Y-%H_%M_%S").gz
        find /mnt/mediacenter/backups/mysql/* -mtime +3 -exec rm {} \;
    

    The next hurdle was dealing with IPv6; most people don't care about it, but I'm not most people! IPv6 is quite complicated (at least to me), so that took a bit of experimenting to get it to work in Docker. For future reference, ndppd lets the virtual machine tell the world that it handles IPv6 for the Docker containers (basically).

    So where was I? After getting the Linux VM setup, it was on to setting up my containers. With docker-compose, I could setup one file that was the configuration for all my containers. Now this was great as I could modify it and test out different containers. After a few days of work, this is the core of my docker-compose file. There are a few other containers I've added including LibreNMS, but this is basically what I have. The nginx-proxy is great as I just add DNS entries for each service and it handles SSL and lets me run multiple web services on the same machine.

    version: "2.3"
    services:
      nginx-proxy:
       image: jwilder/nginx-proxy
       environment:
          - DEFAULT_HOST=pihole.exmple.com
       ports:
         - "80:80"
         - "443:443"
         - "::1:8080:80"
       dns:
         - 10.0.1.1
       volumes:
         - /var/run/docker.sock:/tmp/docker.sock:ro
         - '/mnt/mediacenter/docker/certs:/etc/nginx/certs'
       restart: always
       networks:
          default:
            ipv6_address: XXXX:XXXX:XXXX:XXXX:1::2
  • Setting up a Leviton VRCZ4-M0Z for use with Home Assistant

    I've been so pleased with Home Assistant that I decided to see if I could migrate completely away from Vera and run all my Z-Wave devices on Home Assistant. Currently Home Assistant uses OpenZwave as the base and has basic support for a lot of Z-Wave devices. While OpenZwave isn't as mature as Vera in its implementation, I found that with the exception of 4 Leviton VRCZ4-M0Z zone controllers, everything worked well. I've read that some people have had problems with Z-Wave on Home Assistant, but so far things have been going quite well for me using an Aeotec Z-Stick Gen5 as the controller. I suspect that my success is due to the type of devices I have (only outlets, switches, 1 light bulb, 2 portable MiniMote controllers and these VRCZ4s); I don't have any sensors and only 2 of my devices are battery powered.

    In order to complete my transition away from Vera, I had to get the 4 VRCZ4s to work. A Google search turned up very little information on how to do this with the exception of one post that gave me some clues. The post uses a third party piece of software that I don't have to program the controllers, so that was pretty much out. However, the post did talk about the SCENE_CONTROLLER_CONF Z-Wave command class. With this information in hand, I decided to see if I could program my controllers.

    Here's what I eventually did.

    Controller Programming

    1. Sign up for a developer account on Silicon Labs.
    2. Download the Z-Wave Developer kit, specifically the Z-Wave PC Controller software. (Windows only, but works fine in VMWare Fusion)
    3. Reset the VRCZ4 by pressing and holding the left side of buttons 1 & 3 until it blinks amber and then remains a solid red.
    4. Press left side of buttons 1 & 3 of the VRCZ4.
    5. Add node in Z-Wave config in HA
      Add Node
    6. Wait until node is added. I like to look the the OZW_Log.txt to check status. The node will say Complete next to it when it is done.
    7. Select node in Z-Wave config called Leviton VRCZ4-M0Z. Make a note of the node number.
      VRCZ4 Node
    8. Rename Entity by going to Node Information, selecting the gear and entering a new node.
    9. Under Node Group Associations, select Group 1
    10. Make sure Aeotec Z-Stick (node 1) is selected and click Add to Group.
      Screen Shot 2019 01 03 at 9 33 22 AM
    11. Repeat the above 2 steps for Groups 2-4. The VRCZ4 needs to know where to send commands when the buttons are pressed.
    12. If you have other devices you want to control such as lights or outlets and they support Z-Wave association, associate the devices with the VRCZ4 now. Groups 1-4 corresponds with buttons 1-4. So if you want button 1 to control an outlet, associate it with the outlet.
    13. If all the buttons on the VRCZ4 are going to be associated with other devices, you can stop here as the associations will cause the buttons to turn on/off the associated devices. You can even associate more than 1 device to a button (i.e. 2 backyard lights).
    14. If you want of the buttons to control scripts or automations on Home Assistant, you'll have to do some more steps.
    15. Shutdown the HA box (not just HA, but the entire box).
    16. Connect Aeotec Z-Stick to computer with Z-Wave PC Controller software.
    17. Launch PC Controller software.
      Z-Wave PC Controller
    18. Click Command Classes
    19. Locate node of controller on left side.
    20. Click on it and then click Node Info.
      Command Classes
    21. Double click SCENE_CONTROLLER_CONF in the lower left.
    22. Change command to SCENE_CONTROLLER_CONF_SET.
      Change Command Class
    23. Click the button in the lower right to show the log.
    24. At this point, you need to program each button where you are not using associations to control devices.
    25. The Group IDs for the buttons are as follows:
      Button 1 on - 1
      Button 2 on - 2
      Button 3 on - 3
      Button 4 on - 4
      Button 1 off - 5
      Button 2 off - 6
      Button 3 off - 7
      Button 4 off - 8
      
    26. For each group ID, you have to assign a scene ID. Apparently the scene IDs should be unique across the entire Z-Wave network, but for my case I assigned the same scene IDs for each controller and can distinguish between the controllers in Home Assistant. You can choose whatever numbering scheme you want, but I went with the following:
      Button 1 on - 1
      Button 1 off - 2
      Button 2 on - 3
      Button 2 off - 4
      Button 3 on - 5
      Button 3 off - 6
      Button 4 on - 7
      Button 4 off - 8
      
    27. For each group ID, assign a scene ID and click send.
    28. You'll see some message in the logs.
    29. To verify that the scene IDs are set, press a button and in the log you will see a SCENE_ACTIVATION_SET message with the scene ID.
      Z-Wave Log
    30. Note that the group IDs and the scene IDs are in hex. Since I'm only using 1-8, it doesn't matter but if you use different scene IDs, be aware of this.
    31. Repeat for each button you want to set.
    32. Unplug the Aeotec Stick from the computer and plug it back into HA and reboot HA.

    Using the Scenes

    The programming has now been done, so the next step is to use the new commands in Home Assistant. I use Node-RED and have setup a sequence that handles all of my controllers.

    Node-RED Sequence
    1. Drag an events node (top left in Home Assistant) to the workspace and set it up to only look at zwave.scene_activated events.
    Scene Events
    2. Use a switch node to differentiate between the controllers.
    Switch Node
    3. Use a change node to extract the scene ID.
    Extract Scene ID
    4. Next use a switch node to separate out the scenes. If you didn't follow my scene numbering, you will have to enter whatever scene IDs you used.
    Separate out scenes
    5. Connect up nodes for each scene.

    While this seems like a lot of work, I probably took longer for me to write this up then to actually configure a controller! I'm not an expert in Home Assistant, Node-RED or Z-Wave so send feedback if you have any.

    Enjoy!

  • Auto Layout in a UITableViewCell with an image

    Auto Layout is an amazing concept for developing iOS apps as it allows for an application to more easily look good across different devices. In addition it makes using dynamic type so much easier; as someone that wears glasses I always increase the type size on my devices and when apps don't take advantage of it, I get kind of annoyed. So when I develop apps, I try to use dynamic type and using auto layout makes things so much easier.

    A common theme in apps I develop is to have a UITableView with a bunch of rows. The rows have different bits of text and the only sensible way to develop this is using auto layout. With the latest releases of iOS, a lot of code dealing with dynamic type, heights of cells, responding to device changes, etc. has been eliminated. However, there are still a few gotchas in making an app with a UITableView that behaves properly.

    I'm going to go over the steps I used (and provide sample code) for how I handle this. I had a few requirements that make my implementation a little different than other tutorials on the web.

    • Each cell has an image that would be at most 1/3 the width of the screen.
    • The image must touch the top and bottom of the row.
    • The image had to be at least a certain height.
    • The image should attempt to have the aspect ratio of the original image. (Aspect Fit could leave white space; Aspect Fill could hide some of the image.)
    • Images are loaded asynchronously from the Internet.
    • Next to the image is up to 5 lines of text pinned to the top.
    • Next to the image is 1 line of text pinned to the bottom.
    • Each line of text could wrap to multiple lines.
    • Increasing the type sized must resize the rows.
    • The cell must have a minimum height.
    • Rotating the device must work.

    Writing that out sure looks more complicated than it was in my head!

    I'm not going to go over the initial project setup, but will jump right into Interface Builder after I created a UITableViewCell with xib. Note that I don't use Storyboards and opt to create a separate xib for each view controller and each cell; Storyboards tend to bite me each time I use them as I like to re-use code as much as possible and by default the Storyboard for a UITableViewController puts the cell in the Storyboard making it harder to manage. I'm sure some would argue with me that Storyboards are great, but they just don't work for me.

    1. Create a new UITableViewCell with a xib.
      Creating a UITableViewCell subclass
    2. In the xib, add a UIImageView to the left side that is pinned to the left, top, and bottom of the cell.
    3. Set the UIImageView to Aspect Fill and Clip to bounds.
    4. Give the UIImageView fixed width and height constraints. (We'll change this later.)
    5. Add a vertical stack view pinned to 10 pixels from the UIImageView, pinned to 10 pixels from the top and 10 pixels from the right.
    6. Add 5 UILabels to the stack view. Each label should be set for "Automatically adjusts font" and a Font of Body. Set 1 or more of the labels to have 0 Lines so that it grows vertically. Also, set auto shrink to minimum font scale of 0.5.
    7. Add another vertical stack view pinned to the leading of the first stack view, 10 pixels from the right and bottom of the container and on the top to be >= 5 from the other stack view.
    8. Add a UILabel to this stack view. Set the font as above.
    9. Set the height constraint of the UIImageView to a priority of 250 (low).
    10. Add a UIActivityIndicator that is centered on the UIImageView (set the constraints).
    11. Create a UIImageView subclass that looks like this. The UIImageView uses the intrinsic size of the image in the absence of other constraints and we want more control of the height of the view.
      class ExampleImageView: UIImageView {
          var minimumHeight: CGFloat = 0
          override var intrinsicContentSize: CGSize {
              if minimumHeight == 0 {
                  return super.intrinsicContentSize
              }
              return CGSize(width: 0, height: minimumHeight)
          }
      }
      
    12. Change the UIImageView class to ExampleImageView.

    13. Connect outlets for the 6 UILabels, the UIImageView (with the new class), the activity indicator and the height and width constraints on the UIImageView.

    Your xib should look like this:

    TableViewCell xib

    Time to move into the source of the table view cell. I'm only going to cover the interesting parts here. See my example repo at for the full example.

    1. Setup a variable for the cell width. This is going to be set through the view controller so that rotation changes can change the width of the image.
      var cellWidth: CGFloat = 0 {
          didSet {
              maxImageWidth = cellWidth / 3
              setImageWidth()
          }
      }
    2. Home Assistant and Node-RED, automation perfected?

      Earlier this year I started experimenting with Home Assistant and wrote some thoughts about it. It brought together enough pieces that I keep tinkering with it. A friend of mine had mentioned a project called Node-RED which is supposed to more easily link together IoT components and perform actions based on different inputs. At the time, I brushed it off as I didn't want to bother figuring out how to install it.

      Fast forward to last week when I noticed that Home Assistant had a Node-RED add-on. Installing the add-on was quite easy and I was presented with a blank canvas. After a few minutes, I figured out how to make a simple sequence that took the state of a door sensor (at the time connected via my Vera) and turned on a light. Nothing too fancy, but I was able to hook it up, hit Deploy and test. It worked! This was light years ahead of the YAML based automations in Home Assistant and much faster to setup than Vera. I was hooked almost immediately. Could I convert all my automation logic to this? I spent the next few days trying.

      Wiring some basic automations was quite easy as in this example that turns off lights in my bathroom if there is no motion. Node-RED resets the timer if there is more motion, making the sequence very straightforward.
      Basic Node-RED Sequence

      Unfortunately not all my automations are this simple.

      The most important sequences deal with my front and back motion sensors. I could just use something like the above sequence, but I only want the lights on at night, I want to be able to disable the motion sensors (for example on Halloween if we're not home, I don't want lights coming on), and if I turn on the lights manually I don't want them turning off a few minutes after there is motion. The tricky part here was to determine if the lights were manually turned on or triggered by a motion sensor. After a bit of experimenting, I decided to record the last time there was motion into a variable. Then when the lights turn on I check the variable to see how long ago it occurred. If it was less than 30 seconds ago, call it triggered my motion. With that, I was able to set on off timer based on how the lights came on. The one bit of "code" I had to write was to determine how long ago the motion was tripped.

          var motionTimestamp = flow.get('lastFrontMotion') || -1;
          var difference = (Date.now() - motionTimestamp) / 1000;
          if (motionTimestamp === -1)
          {
              difference = -1
          }
          msg.payload = difference;
          return msg;
      

      The flow may look complicated, but to me it is quite readable.

      Complicated Node-RED Flow

      The visual aspect of Node-RED makes it easier to setup automations, but calling automation simple is far from the truth as I've shown above. As a professional software developer, writing code doesn't scare me but for a hobby, this visual approach (with a little code as necessary) is much nicer. When I come back to this in 6 months, I have no doubt that I can read what is going on and troubleshoot as necessary.

      For the last 5 years, I've been using my Vera to control everything using a plugin called PLEG which stands for Program Logic Event Generator. It has worked quite well, but it has been so long since I setup most of the automations, I have no idea what I did. PLEG, while functional, was a bit difficult for me to wrap my head around and it pained me every time I had to touch it. Also, when I touched it, I seem to recall having to restart Vera and wait only to find out that I needed to change something.

      I'm so impressed with Node-RED that I've decided to see if I can move all my Z-Wave devices to Home Assistant using an Aeotec Z-Stick Gen5 plugged into the Home Assistant. The goal with this move is to speed up messaging; right now the Home Assistant polls (I think) Vera all the time looking for changes. This isn't very efficient and pressing a button can take a second or two to have the message reach Home Assistant. Will this work? I hope so!

      I know that I'm just scratching the surface with this, but I am very excited over the prospects!