Basic Hue Lighting Control: Part 2

This is the second part of a two-part post.  The first part can be found here.

At the end of the first part, I had gotten discovery of the bridge implemented and had performed the pairing of the bridge.  In this part, I will show you how to create a query for the state of the light groups and control them.

Querying Group State

I’m only allowing the modification of the state of groups of lights on the Hue.  First I need to query the bridge for what states exist.  The list of groups and the state of the group are available at `http://${this.ipAddress}/${this.userName}/groups`. Here the data in this.userName is the user name that was returned from the Hue bridge in the pairing process.  With this information I am able to create a new UI element for each group found.  I only show groups of type “room” from this response.  It is also possible that the user has grouped an arbitrary set of lights together in a group.  I don’t show these.

var hueDB = (function () {
    var db = {};
    var datastore = null;
    var version = 1;
    db.open = function (callback) {
        var request = indexedDB.open('hueDB', version);
        request.onupgradeneeded = function (e) {
            var db = e.target.result;
            e.target.transaction.onerror = db.onerror;
            var store = db.createObjectStore('bridge', { keyPath: 'bridgeID' });
        };
        request.onsuccess = function (e) {
            datastore = e.target.result;
            callback();
        };
    };

    db.getBridgeList = function () {
        return new Promise((resolve, reject) => {
            var transaction = datastore.transaction(['bridge'], 'readonly');
            transaction.onerror = function (e) {
                reject(e.error);
            };
            transaction.oncomplete = function (e) {
                console.log('transaction complete');
            };

            var objStore = transaction.objectStore('bridge');
            objStore.getAll().onsuccess = function (e) {
                console.log('bridge retrieval complete');
                resolve(e.target.result);
            };

            var bridgeList = [];


        });
    };

    db.addBridge = function (bridge) {
        console.log('adding bridge ', bridge);
        return new Promise((resolve, reject) => {
            var transaction = datastore.transaction(['bridge'], 'readwrite');
            transaction.onerror = function (e) {
                reject(e.error);
            };
            transaction.onsuccess = function (e) {
                console.log('item added');
            };
            var objStore = transaction.objectStore('bridge');
            var objectStoreRequest = objStore.add(bridge);
            objectStoreRequest.onsuccess = function (e) {
                resolve();
            };
        });
    };

    return db;
})();

Changing the State of a Light Group Attributes

There are several elements of a light group’s state that can be modified.  I’m only considering two: the brightness of the light group and whether or not the group of lights is turned on.  Both can be set with a PUT request to the bridge at the the URL http://${this.ipAddress}/${this.userName}/groups/${id}/action`.  This endpoint accepts a JSON payload.  Turning a group of lights on or off; changing the brightness; activating a scene to change the color; and many other options can be changed through this end point.  It is not necessary to specify all of the possible attributes when calling this endpoint.  If an attribute is not specified it will remain at its current state.  I have made a method named setGroupState that will be used by all other methods that make use of this endpoint.  The methods will differ in the payloads that they build and pass to this method.

    setGroupState(groupName, state) {
        var id = this.groupToGroupID(groupName);
        var reqBodyString = JSON.stringify(state);
        return new Promise((resolve, reject) => {
            fetch(`http://${this.ipAddress}/api/${this.userName}/groups/${id}/action`, {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: reqBodyString
            })
                .then(resp => resp.json())
                .then(jsonResp => {
                    resolve(jsonResp);
                })
                .catch(err => reject(err));
        });
    }

Of the many attributes that could be packaged in the payload are bri and on.  The on state sets whether or not the lights are turned on.  The bri attribute accepts a value in the range of 0 to 254.  Note that a value of 0 doesn’t mean off.  Zero is the value level assigned to the lowest level of illumination above off that the light will provide.

Activating Scenes

Scenes, or a collection of settings that applies to lights, can by associated with a predefined light group or with some arbitrary group of lights.  The Hue API labels scenes as either LightScene or GroupScene accordingly.  I am only working with groups scenes.  A list of all of the scenes defined on the bridge is retrievable through the the endpoint http://${this.ipAddress}/api/${this.userName}/scenes.

The object returned is a dictionary of the scene IDs and the attributes.  The scene ID is a string of what appears to be random characters.  It’s not user friendly and should only be used internally by the code and never presented to the user.   Here is a response showing only two scenes.

{
    "8AuCtLbIiEJJRNB": {
        "name": "Nightlight",
        "type": "GroupScene",
        "group": "1",
        "lights": [
            "2"
        ],
        "owner": "rF0JJPywETzJue2G8hJCn2tQ1PaUVeXvgB0Gq62h",
        "recycle": false,
        "locked": true,
        "appdata": {
            "version": 1,
            "data": "5b09D_r01_d07"
        },
        "picture": "",
        "lastupdated": "2017-01-16T23:35:24",
        "version": 2
    },
    "7y-J6Qyzpez8c2R": {
        "name": "Dimmed",
        "type": "GroupScene",
        "group": "1",
        "lights": [
            "2"
        ],
        "owner": "rF0JJPywETzJue2G8hJCn2tQ1PaUVeXvgB0Gq62h",
        "recycle": false,
        "locked": false,
        "appdata": {
            "version": 1,
            "data": "Nmgno_r01_d06"
        },
        "picture": "",
        "lastupdated": "2017-01-16T23:35:24",
        "version": 2
    }
}

To activate a scene on a group I use the same endpoint that is used for turning light groups on and off or setting their brightness level.  The JSON payload will have a single element named scene whose value is one of the cryptic looking scene identifiers above.

    activateScene(sceneID) {
        var scene;
        if(sceneID in this.sceneList) {
            var scene = this.sceneList[sceneID];
            var group = scene.group;
            var req = {scene:sceneID};
            return this.setGroupState(group,req );            
        }
    }

Application Startup

To hide some of the events that occur at startup the application has a splash screen. The splash screen is only momentarily present. During the time that it is momentarily shown the application will attempt to reconnect to the last bridge that it had connected to and will query for the available groups and scenes. This is just enough of a distraction to hide the time taken to do this additional setup.

switch
The Application Splash Screen

Installing and Running the Application

If you have downloaded the source code to your local drive, you can add the program to Chrome as an unpacked extension. In a browser instance open the URL chrome://extensions.  In the upper-left corner of this UI is a button labeled Load Unpacked.  Select this option.

unpacket
UI for loading unpacked Chrome extensions

You will be prompted to select a folder.  Navigate to the folder where you have unpacked the source code and select it.  After selecting it you will see the application in the list of installed extensions.

loadedextension

The application will now show up in the Chrome app launcher.  This may be exposed through the regular app launcher that is part of your operating system (such as the Program menu on Windows) and will also appear in Chrome itself.  Close to the address bar is a button labeled “Apps.”

applauncher
The application in the Chrome app launcher

Completing the Application

As I mentioned in the opening,  this is not meant to be a complete application.  It is only an operational starting point, creating something that is functional enough to start testing different functions in the Hue API.

I will close with mentioning some other potential improvements.  For a user running the application for the first time the setup process might be smoothed out by automatically trying to pair with the first bridge seen (if there is only one bridge seen) and prompting the user to press the link button.  This makes the setup process a two step process: start the application and press the link button on the bridge.  There could also be other people that are operating the Hue lighting at the same time that this application is running.  Periodically polling the state of the lights and light groups on the network and updating the UI accordingly would improve usability.  A user may also want to control individual lights within a group or have control over the light color.  For this a light selection UI would also need to be developed.

It took me about an evening to get this far in the development and it was something enjoyable to do during a brief pause between projects.  As such projects go, I’m not sure when I’ll get a change to return to it.  But I hope that in it’s current form that it will be of utility to you.

-30-

 

Advertisements

Connecting to Bluetooth RFCOMM with UWP

Soon I will be finished with a project for streaming engine data from a car and into an Azure Data Lake for analysis. I plan to post the full code for this project. But before I do I wanted to talk just about making the Bluetooth connection. The last time I wrote on this procedure it was for a Windows phone 8 project. While part of the UWP ancestry the method of connection in that project would not work in a UWP application today. Things have changed since then and the code that I used there for establishing a connection would not work today.  Treat the code used in this project as psuedo code for now. While it was copied from a working project I’ve done some modifications to simplify it and focus on a few things here.

Getting information from the engine is easy. Every car sold in the USA (and many  parts of the world) have a diagnostic connector that can be used to query various data from the car. Getting this data is just a matter of having an interface that uses what ever protocol (there’s more than one) that your car happens to use. Don’t worry, it’s not necessary to figure this out yourself. There are products that will abstract this away and provide a common interface and protocol that can be used to communicate with all of these different protocols. One of the most popular solutions is based on a chipset known as the ELM327. Solutions based on the ELM327 (or an ELM327 clone) will give you a serial interface through which you can send AT commands and other queries to get this information. The products that I am using use a Bluetooth interface for communicating with a computing device. You can also find implementations that use WiFi and RS232.

For development and testing there’s going to be a lot of time spent with a computer in or near a car (unless you also go out and purchase an OBD emulator). To minimize the amount of time that you have to spend around a car you may want to power one of the adapters outside of a car. I did this using a battery (sometimes a power supply) and and an OBD wire harness. Pin number 4 on the harness needs to be connected to ground and pin 16 to positive. A voltage range of 9 to 16 volts should work fine. With the device connected to power you can query information about the device itself but nothing about the car.  While this doesn’t sound very useful at first it’s enough to get the basic Bluetooth connectivity code working.

OBDSetup

For development hardware I am using a Dragonboard 410x running Windows IOT. The UWP based solution, while targeting Windows IOT, runs just fine from a PC. I believe it will run fine from a Raspberry Pi 3 also. Just for fun I tried an old Windows 10 device that I still have in the house and the code ran without any modifications needed from there. So grab what ever Windows 10 device that you have and try it out!

For the application to be able to connect to the device over Bluetooth there are some capabilities that must be declared in the UWP application. I don’t suggest using the Visual Studio for doing this. It doesn’t expose all of the necessary capability settings that are needed. It’s easier to do this in the text editor.

Right-clicking on the project’s manifest gives the option to open it in the code editor. Scrolling down to the Capabilities section I added the following. Some of these properties are needed to access the Bluetooth serial port. Some others are there because they are parts of the project that I will talk about in future post.

  
  <Capabilities>
    <apability Name="internetClient" />
    <DeviceCapability Name="bluetooth" />
    <DeviceCapability Name="proximity" />
    <DeviceCapability Name="location" />
    <DeviceCapability Name="bluetooth.rfcomm">
      <Device Id="any">
        <Function Type="name:serialPort" />
      </Device>
    </DeviceCapability>
  </Capabilities>

I had already paired the Bluetooth device with my Windows IOT device. While the devices are paired with each other the application doesn’t know about the pairing. It still needs to discover the device. To do this I use a device watcher to scan for devices. I know the one that I want to find is named “OBD”. I’ve hard coded this into my solution. The end solution that I’m making doesn’t have a UI, If it did then I would have also made use of the UI that allows a user to select a device being paired.

DataReader _receiver;
DataWriter _transmitter;
StreamSocket _stream;
DeviceWatcher _deviceWatcher;

string[] requestedProperties = new string[] { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };
_deviceWatcher = DeviceInformation.CreateWatcher("(System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\")",
                                               requestedProperties,
                                               DeviceInformationKind.AssociationEndpoint);
_deviceWatcher.Stopped += (sender,x)=> {
    _isScanning = false;
    Log("Device Scan Halted");
};
EngineDataList.Add("started");
_deviceWatcher.Added += async (sender, devInfo) =>
{
    Log($"Found device {devInfo.Name}");
    System.Diagnostics.Debug.WriteLine(devInfo.Name + "|" + devInfo.Kind + "|" + devInfo.Pairing);
    if (devInfo.Name.Equals("OBDII"))
    {
     // More Code Goes Here
     //Lets talk about that in a sec
    }
};

_deviceWatcher.Start()

The above code will give result in the DeviceInfo object for the Bluetooth adapter being returned. Now we can connect to it and open up streams for reading and writing. The code for creating these streams would be where I have the comment placed in the above. The following is the code that would go in that place.

try
{
    DeviceAccessStatus accessStatus = DeviceAccessInformation.CreateFromId(devInfo.Id).CurrentStatus;
    if (accessStatus == DeviceAccessStatus.DeniedByUser)
    {
        Debug.WriteLine("This app does not have access to connect to the remote device (please grant access in Settings > Privacy > Other Devices");
        return;
    }
    var device = await BluetoothDevice.FromIdAsync(devInfo.Id);
    Debug.WriteLine(device.ClassOfDevice);

    var services = await device.GetRfcommServicesAsync();
    if (services.Services.Count > 0)
    {
        Log("Connecting to device stream");
        var service = services.Services[0];
        _stream = new StreamSocket();
        await _stream.ConnectAsync(service.ConnectionHostName,
        service.ConnectionServiceName);
        _receiver = new DataReader(_stream.InputStream);
        _transmitter = new DataWriter(_stream.OutputStream);
        
        ReceiveLoop();
        QueryLoop();
        _deviceWatcher.Stop();
    }
}catch(Exception exc)
{
    Log(exc.Message);
}
catch
{
    Log("Native Error");
}

After this runs the devices are connected to each other. The devices would interact by the Win IOT device sending a query and getting a response. For now the only query that will be sent is the string “AT\n”. On devices that accept AT command sets the command AT should only result in the response OK. This is useful for testing the connection without actually having the device perform an operation that would change the state of the system being used.

void QueryLoop()
{
    Task t = Task.Run(async () =>
    {
        
        while (true)
        {
            _transmitter.WriteString(msg);
            _transmitter.WriteString("\r");
            await _transmitter.StoreAsync();
            await Task.Delay(50);
        }
    }
    );
}

void ReceiveLoop()
{
    Task t = Task.Run(async () => {
        while (true)
        {
            uint buf;
            buf = await _receiver.LoadAsync(1);
            if (_receiver.UnconsumedBufferLength > 0)
            {
                string s = _receiver.ReadString(1);
                receiveBuffer.Append(s);
                if (s.Equals("\n")||s.Equals("\r"))
                {
                    try
                    {
                      Debug.WriteLine("Message Received:"+receiveBuffer.ToString());
                        receiveBuffer.Clear();
                    }
                    catch(Exception exc)
                    {
                        Log(exc.Message);
                     
                    }
                }
            }else
            {
                await Task.Delay(TimeSpan.FromSeconds(0));
            }
        }
    });
}

When the above runs the debug output stream will print a series of OK responses. To do something more useful with it we need to know what commands to send to it and how to parse the responses. If you send the command 01 0C while the adapter is connected to a car you will get back a hex response that starts with 41 0C followed by some additional hex numbers. The additional hex numbers are the RPMs of the car. That’s one of the metrics I started off querying because it’s something that I can change without driving the car; for this I can pull the car out of the garage, put it in the driveway, and rev the engine without worrying about carbon monoxide poisoning. I’ll talk about how to find out what metrics that you can query in your car in my next post on this project.

Simplified UWP View Mode Base

If you’ve implemented a ViewModelBase (or some object that implements INotifyPropertyChanged) in UWP there’s a pattern that you will recognize. When a property value is being set you might check to see if the new value is different from the previous value, if it is then you assign the value to the property and call the OnPropertyChanged event. Such code might look something like this.

public abstract class ViewModelBase: INotifyPropertyChanged {
   public event PropertyChangedEventHandler PropertyChanged;
   protected OnPropertyChanged(String propertyName)
   {
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
}

public class Employee : ViewModelBase{
   private int _employeeID; 
   public int EmployeeID
   {
      get { return _employeeID; }
      set 
      {
         if(_employeeID != value )
         {
            _employeeID = value;
            OnPropertyChanged("EmployeeID")
         }
      }
   }
}

While it works it could be better. The first improvement is in the implementation of OnPropertyChanged. If someone my a typo in the name of the property it might not noticed until runtime debugging (if at all). It would be better if such an error were caught at compile time. There’s a different implementation of OnPropertyChanged that can do this.

protected void OnPropertyChanged(Expression expression)
{
   OnPropertyChanged(((MemberExpression)expression.Body).Member.Name);        
}

With this implementation instead of typing the name of the property we can type an expression for the property. If we make a mistake in the expression it will cause a compile time error. Out Employee class will now look like the following.

public class Employee : ViewModelBase{
   private int _employeeID; 
   public int EmployeeID
   {
      get { return _employeeID; }
      set 
      {
         if(_employeeID != value )
         {
            _employeeID = value;
            OnPropertyChanged(()=>EmployeeID)
         }
      }
   }
}

That’s improved, but there’s still room for improvement. Checking for the value to be different and then conditionally assigning the value can be modularlized so that we don’t have to keep typing it. To do this I add a couple of additional methods to the ViewModelBase.

protected bool SetValueIfChanged(Expression<Func> propertyExpression, Expression<Func> fieldExpression, object value)
{
    var property = (PropertyInfo)((MemberExpression)propertyExpression.Body).Member;
    var field = (FieldInfo)((MemberExpression)fieldExpression.Body).Member;
    return SetValueIfChanged(property,field, value);
}

protected bool SetValueIfChanged(PropertyInfo pi,FieldInfo fi, object value)
{
    var currentValue = pi.GetValue(this);
    if ((currentValue == null && value == null)||(currentValue!=null &&  currentValue.Equals(value)))
        return false;
    fi.SetValue(this, value);
    OnPropertyChanged(pi.Name);
    return true;
}

The second version of this method is probably easier to understand. It uses classes from the reflection namespace to interact with the object. This allows it to operate on on any properties or fields as long as the reflection data is passed. The first implementation of this method takes an expression and gets the reflection data from it. Applying this to the Employee class it now looks like the following.

public class Employee : ViewModelBase{
   private int _employeeID; 
   public int EmployeeID
   {
      get { return _employeeID; }
      set { SetValueIfChanged(()=>EmployeeID, ()=>_employeeID, value); }
   }
}

Much better. Now if I want to reduce my typing even more instead of naming the method SetValueIfChanged I could name it something much shorter. One must still define the the fields that backup these properties. Rather than typing them out a code snippet could be defined for this pattern. A code snippet is like a template that is associated with a keystroke. I’ll talk about that in another post.