Skip navigation

Pi IoT

August 21, 2016 Previous day Next day

From my holiday location at the coast of Bulgaria I finished up the implementation of the UI of Thuis. It works nicely on both the iPhones and the iPad on the wall. This blog will give you a demo and bring you up-to-date with some of the changes I made since the last post.

 

iPad on the wall

 

Adding the Slider Tile

I already implemented the UICollectionViewController and several tiles in the last blog post, however it was lacking the SliderTile implementation. For this several changes were needed, for example a tile should be able to span multiple columns to create enough space for the slider. For this a new property is added to Tile called columns. It now looks like this:

class Tile {
    let title: String
    var icon: UIImage?
    var value: String?

    let columns: Int

    let topics: [TopicType: String]

    init(title: String, icon: UIImage, columns: Int, topics: [TopicType: String]) { ... }
    convenience init(title: String, icon: ionicon, columns: Int, topics: [TopicType: String]) { ... }
}

 

UICollectionViewController can handle cells of different sizes,  so making sure they display correctly is luckily easy:

extension TilesCollectionViewController: UICollectionViewDelegateFlowLayout {
   func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {   
      let tile = tiles[indexPath.row]
      let height = self.view.bounds.height / 6.0

      switch (tile.columns) {
      case 1:
         return CGSize(width: self.view.bounds.width/2.0 - 12.0, height: height);
      case 2:
         return CGSize(width: self.view.bounds.width - 16.0, height: height);
      default:
         print("Not supported amount of columns for button");
         return CGSize.zero
      }
   }
}

 

Now we can actually implement the SliderTile itself. First we add the slider to the UI:

TileCollectionViewCell.xib with Slider

 

The TileCollectionViewCell class got some more functionality now. To make things easier I moved some of the logic from the TilesCollectionViewController to here. It now takes care of deciding what to show and what to hide. For example the value will be hidden when there is no value (for buttons) or when a slider is used (the slider already visualizes the value).

 

To handle changes of the value when someone uses the slider a delegate method had to be added. The slider is linked to the delegate method in the TileCollectionViewCell, but this delegates the actual work to the SliderTile. This class also has some properties defining the slider, such as the minimum and maximum allowed values, and takes care of updating the slider when a MQTT message arrives.

class SliderTile: Tile, TileCollectionViewCellDelegate, MQTTSubscriber {
    let minimumValue: Float
    let maximumValue: Float
    let rounded: Bool

    var publishDelayTimer: Timer?
    
    init(title: String, icon: UIImage?, columns: Int, topic: String, minimumValue: Float, maximumValue: Float, rounded: Bool) {
        var topics = [TopicType: String]()
        topics[.status] = topic
        topics[.set] = "\(topic)/set"
        
        self.minimumValue = minimumValue
        self.maximumValue = maximumValue
        self.rounded = rounded
        
        super.init(title: title, icon: icon, columns: columns, topics: topics)
    }

    convenience init(title: String, icon: ionicon, columns: Int, topic: String, minimumValue: Float, maximumValue: Float, rounded: Bool) {
        self.init(title: title, icon: nil, columns: columns, topic: topic, minimumValue: minimumValue, maximumValue: maximumValue, rounded: rounded)
        self.icon = icon
    }

    func didReceiveMessage(_ message: MQTTMessage) {
        if topics.values.contains(message.topic) {
            guard let payloadString = message.payloadString else {
                print("Received empty message for topic '\(message.topic)'")
                return
            }
            
            if rounded {
                if let _ = Int(payloadString) {
                    self.value = payloadString
                } else {
                    print("Received invalid message for topic '\(message.topic)': \(payloadString)")
                }
            } else {
                self.value = payloadString
            }
        }
    }
    
    func sliderValueDidChange(_ sender: UISlider!) {
        if rounded {
            value = "\(Int(sender.value))"
        } else {
            value = "\(sender.value)"
        }

        if let topic = topics[.set],
           let payloadString = value {

            // Invalidate any previous requests
            if let timer = publishDelayTimer {
                timer.invalidate()
            }

            publishDelayTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
                MQTT.sharedInstance.publish(payloadString, topic: topic, retain: false)
            }
        }
    }
}

 

Adding a optional postfix to values

Some of the InfoTiles show the weather, and for the temperature it's a lot clearer when we include a degree sign next to the value. So I've added an optional postfix to the value of a Tile. This is a simple String property which is added at the end of the value before display:

valueLabel.text = tile?.value

if let value = tile?.value, let valuePostfix = tile?.valuePostfix {
    valueLabel.text = value + valuePostfix
}

 

Changes to support iPad

The iPhone and iPad have a very different screen size and of course we don't want to duplicate any code, or add checks everywhere. Luckily for us Apple solved most of this by providing adaptive layouts and layout constraints. We'll use the latter to slightly change the layout of the TileCollectionViewCell to adapt to the bigger screen. Apple differentiates between devices by using so-called size classes, which are very well described in The Adaptive Model. There are two changes we have to make: change the icon size and the width of the slider. The icon will be 45 points on compact-width devices and 70 on regular-width devices. The slider will get a width of 200 points on compact-width devices and 400 on regular-width devices.

 

Determining the size of the cell was already working well on all devices as it's using a size based on the size of the superview. You can see the implementation of the slider above.

 

Demo

The implementation of the design Marina made for the Thuis app is now implemented, and how better to show it then with a demo:

 

In the demo you can see the different screens on both an iPad and an iPhone. At the bottom you can see the MQTT messages being send and received. Notice that when clicking a button a message is send to the set-topic (for example Thuis/device/living/moodTop/set) by the iOS app. Then a message is received on the status topic for that device (Thuis/device/living/moodTop), which is sent by Zway.

 

When a scene is triggered (in this case Mood, which are the mood lights in the living room) you see the Core sends a message for each device in that scene. The status is also reflected on the iPhone. The slider for the main light in the dining sends out messages every time when you pause for a tenth of a second.

 

This should give you a good overview of the app and its design. Off to the next use case!

 

Starting this challenge, I set out to build not one, but two control units. The idea behind this was that a single control unit would require the user to move to that room in order to be able to trigger actions (aside from using a smartphone of course). That's why I planned to have a control unit in the bedroom (the alarm clock) and one in the living room. The hard part, figuring out the software side of things, has already mostly been done while developing the alarm clock. This can now easily be reproduced for a second control unit, using the puppet modules i created as part of the challenge. The biggest difference is that this second unit will provide a touch screen interface, making more data available at a glance, than the alarm clock.

 

Let's see what the main differences will be

 

Touch Screen

 

This control unit will make use of the Raspberry Pi 7” Touchscreen Display as provided in this challenge's kit. You may already have seen it make an appearance in some demo footage when I was demonstrating the EnOcean sensors or Tower Light in OpenHAB.

Because the touch screen's resolution is limited, different views will be created, each focusing on a different aspect of my smarter spaces.

 

The browser will be used in full screen, kiosk mode, similar to what Rick has done in his [Pi IoT] Hangar Control #5.1 -- Raspberry Pi Kiosk, The Movie  post. A mechanism will be foreseen to switch between different web pages, as kiosk mode hides all navigation bars and buttons, in exchange for more screen space.

 

Button Matrix

 

What's a design challenge, if not the chance to experiment with new things? I came across this interesting I2C 4x4 keypad with silicone elastomer buttons and integrated LEDs, called Trellis. You can pick any 3mm diffused LED to suit your project and solder them on the board. Obviously, I picked white for this project The LEDs can be controlled independently of the buttons as well, making it possible to blink a button in order to draw attention to a certain action, or keep the last pressed button lit for example.

 

Similarly to the 8x8 LED matrix, pads can be shorted on the back using solder, in order to change the I2C address. As I already used 0x70 for the 7-segment display and 0x71 for the 8x8 LED matrix, I decided to use 0x72 for this keypad. This would allow me to combine all three in the code without creating any conflicts.

 

Here's a quick animation of the keypad's example program:

trellis_gif.gif

 

Enclosure

 

This unit will obviously also require an enclosure. The same wood and acrylic highlights will be used, making a consistent set I'm still trying to figure out which shape to go for and how to tackle it, but here's a first attempt at the front panel:

IMG_2180.JPGIMG_2178.JPG

 

To gain on milling times, I'm trying a combination of CNC routing for the outer edges, and manual routing to create grooves and depth. This drastically reduces CNC time!

 

Time is running out!

 


arrow_prev.png

 


Navigate to the next or previous post using the arrows.

arrow_next.png