Moving from local LOE to local LOE

How Do I Ensure My Mesh Object Is Available Everywhere I Need It?

Let’s say you have a scenario in which you have a regular WPF client application which is going to create and manage its own mesh objects in the local Live Operating Environment (LOE) on the device. Now let’s say you install that same application on another device which is also connected to your mesh and is running its own synchronize LOE.  Your application will start up, find the mesh objects it wants to use, but it will discover that none of the data feeds or entries are available as it is expecting. What is going on here?

This is because before the feeds of a Mesh Object can synchronize to another device, you will need to map the Mesh Object to that particular device. Currently, any Mesh Object created on any device will get mapped into the cloud, but will not get mapped down to any other devices in the user’s Device Ring.

Also, note that the mesh objects will always be visible regardless of whether they are mapped. This means that on the other devices, you will see the top level Mesh Object in the local LOE but you will not see the contained Data Feeds unless the device is mapped to the Mesh Object.

In the following example we will create an app that uses a Mesh Object named “MultiDeviceObject” which contains a Data Feed named “MultiDeviceFeed” which contains a single Data Entry named “MultiDeviceEntry.”  In order to create an application that can run anywhere and always have its data available, we need to make it a little smarter about how it handles device mappings. There are 3 scenarios which we will need to handle:

1. The application has never been run before and therefore none of its feeds or Mesh Objects exist.

2. The application has been run on another device so the Mesh Object will exist but has not been mapped to the current device.

3. The application has been run on the device and therefore the Mesh Object and the Data Feeds are available.

We will create a method that is run at the startup of the application that will determine which scenario we are in and will proceed to properly load the Data Feeds the application needs to function. Regardless of the scenario we are going to put the mesh object into a local variable and will read its data in a central location – a method called ReadDataFeeds(). So in all of our scenarios, the goal is to get to the following bit of code which assumes that the data is there and not have to worry again for the lifetime of the app whether the local device is mapped or not to the Mesh Object in question.

 private static MeshObject meshObject = null;

static void ReadDataFeeds()
{
    // do something with the Mesh Object’s feeds
    foreach (var feed in meshObject.CreateQuery<DataFeed>())
    {
        Console.WriteLine("found data feed {0}", feed.Resource.Title);
    }
}

 

 

We just need to make sure that the variable meshObject is populated and mapped properly before ReadDataFeeds() gets invoked.

We have 3 scenarios to handle, let’s take them one at a time.

Scenario 1: The application has never been run before anywhere

In this case, because the application has never been run before, therefore the Mesh Object and its data will not have been created yet. The application needs to create them in the standard way.  Since the Mesh Object’s top level metadata will be synced everywhere we just need to check for its existence.

 string objectName = "MultiDeviceObject"; 

// is the mesh object available at all? 
meshObject = mesh.FindMeshObject(objectName); 
if (meshObject == null) 
{ 
    // Create the object and the feeds 
    meshObject = mesh.FindMeshObject(objectName, true); 
    DataFeed datafeed = meshObject.FindDataFeed("MultiDeviceFeed", true); 
    DataEntry dataentry = datafeed.FindDataEntry("MultiDeviceEntry", true); 
    meshObject.Update(); 
    ReadDataFeeds(); 
}

 

This method will first make an attempt to find our Mesh Object which is named “MultiDeviceObject”. If it fails to find the object then we know it doesn’t exist anywhere and we can just create it and its child objects. Once the data is complete, we call ReadDataFeeds() and continue normally.

Scenario 2: The application has been run on another device but not on the current device

In this case, because the application has been run on another device, the Mesh Object will exist in the Mesh. But because it has not been mapped to this device, only the top level meta data about the Mesh Object will by synced to the local LOE. The Data Feed and the Data Entry we created on the other device will not be found locally. The following two screenshots which demonstrate this, were taken on two different computers. The first is from the machine that the Mesh Object was created and the second is from a another computer moments later after synchronization has finished. Notice that the Data Feed “MultiDeviceFeed” highlighted in the first does not appear in the second.

clip_image001

clip_image002

In order to detect this condition we need to check the Mesh Object and need to determine if it has been mapped to the local device. We can do that by querying the device mappings and searching for one that has the same DeviceLink as the SelfLink of the Mesh’s LocalDevice.

 // check if the device has been mapped yet
var mappinQuery = 
    from mapping in meshObject.CreateQuery<Mapping>().Execute()
where (mapping.Resource.DeviceLink != null) && // a device may be null
(mapping.Resource.DeviceLink.AbsoluteUri == 
    mesh.LocalDevice.Resource.SelfLink.AbsoluteUri)
select mapping;

Mapping deviceMapping = mappinQuery.FirstOrDefault();

 

In our current scenario (the device has never been mapped to the Mesh Object) deviceMapping will be null. If it has some non-null value then we know that the device has already been mapped at some time in the past and should be fully synchronized. Once we know that we need to map the device, we just create a new mapping and add it to the Mesh Object.

 if (deviceMapping == null)
{
    // DataFeeds.ChangeNotificationReceived event will be 
    // raised when the data feeds are available
    meshObject.DataFeeds.ChangeNotificationReceived += 
        new EventHandler(DataFeeds_ChangeNotificationReceived);
    // map the mesh object to the current device
    MeshDevice meshDevice = mesh.LocalDevice;
    deviceMapping = new Mapping(meshDevice.Resource.Title + "_CustomMapping");
    deviceMapping.Device = meshDevice;
    meshObject.Mappings.Add(ref deviceMapping);
}

There is one other line of code that is very important in the last snippet – the change notification. Once the device is mapped, we will need to wait for the local LOE to sync the rest of the contents into the DataFeeds collection. We will know that has happened via the ChangeNotificationRecieved event which will be raised whenever the DataFeeds collection changes. Since the collection is currently empty, any change means that the data is now available. We can then call ReadDataFeeds() from the event handler and continue on our way.

 static void DataFeeds_ChangeNotificationReceived(object sender, EventArgs e)
{
    ReadDataFeeds();
    meshObject.DataFeeds.ChangeNotificationReceived –= 
        new EventHandler(DataFeeds_ChangeNotificationReceived);
}

Scenario 3: The Mesh Object exists and has been mapped to the local device

The last scenario is the most common. Once the application has been run on any particular device, it will have either created the Mesh Object or mapped it to the local device. In this case we will both (a) find the Mesh Object and (b) find the Device Mapping that matches the local device. If this is the case, we simply load the data from the Mesh Object.

 // device was already mapped
ReadDataFeeds();

 

Final Solution: Handle all 3 scenarios

If we take the previous code sections and put them all together it looks like this, which will run and load the data feeds regardless of which machine originally created them.

 private static MeshObject meshObject = null;
    
private static void LoadMeshObject(Mesh mesh)
{
    string objectName = "MultiDeviceObject";
    // is the mesh object available at all?
    meshObject = mesh.FindMeshObject(objectName);
    if (meshObject == null)
    {
        // Create the object and the feeds
        meshObject = mesh.FindMeshObject(objectName, true);
        DataFeed datafeed = meshObject.FindDataFeed("MultiDeviceFeed", true);
        DataEntry dataentry = datafeed.FindDataEntry("MultiDeviceEntry", true);
        meshObject.Update();
        ReadDataFeeds();
    }
    else
    {
        // check if the device has been mapped yet
        var mappinQuery = 
            from mapping in meshObject.CreateQuery<Mapping>().Execute()
        where (mapping.Resource.DeviceLink != null) && // a device may null
        (mapping.Resource.DeviceLink.AbsoluteUri == 
            mesh.LocalDevice.Resource.SelfLink.AbsoluteUri)
        select mapping;
        
        Mapping deviceMapping = mappinQuery.FirstOrDefault();
        
        if (deviceMapping == null)
        {
            // DataFeeds.ChangeNotificationReceived event will
            // be raised when the data feeds are available
            meshObject.DataFeeds.ChangeNotificationReceived += 
                new EventHandler(DataFeeds_ChangeNotificationReceived);
            // map the mesh object to the current device
            MeshDevice meshDevice = mesh.LocalDevice;
            deviceMapping = 
                new Mapping(meshDevice.Resource.Title + "_CustomMapping");
            deviceMapping.Device = meshDevice;
            meshObject.Mappings.Add(ref deviceMapping);
        }
        else
        {
            // device was already mapped
            ReadDataFeeds();
        }
    }
    Console.WriteLine("Press enter to exit.");
    Console.ReadLine();
}

static void DataFeeds_ChangeNotificationReceived(object sender, EventArgs e)
{
    ReadDataFeeds();
    meshObject.DataFeeds.ChangeNotificationReceived –= 
        new EventHandler(DataFeeds_ChangeNotificationReceived);
}


static void ReadDataFeeds()
{
    // list out all the feeds
    foreach (var feed in meshObject.CreateQuery<DataFeed>())
    {
        Console.WriteLine("found data feed {0}", feed.Resource.Title);
    }
}

When run on two different machines in a console application, the output looks like this

Run on the 1st device and it will create the mesh object, feed, and entry

clip_image003

Run on the 2nd device and it will discover the mesh object and then map the local device to it then wait for the data feeds to sync. That is why the order of the lines is different.

clip_image004

Run on the 2nd device again and it will find all the objects immediately

clip_image005

NOTE: The above code snippets use my helper library to find and/or create the various Live fx objects.