Map your devices with Live Framework
I’m glad to see that this blog is getting quite popular, so I’m going to keep the same strain for the moment and extend the MeshageBoard walkthrough once again.
The code written so far for the cloud, so that all the data we were adding and viewing was on the internet… however the local mesh client also produces a feed, but only for a specific subset of objects (i.e. those which have been mapped).
Mapping can be done with folders (at the moment on the beta but not with the developer preview) on the fly by right clicking a meshed folder and choosing sync options.
The mappings can also be maintained programmatically with the Mappings collection under MeshObjects.
A good quick example for this is Mike Taulty’s channel 9 vid:
https://channel9.msdn.com/posts/mtaulty/Live-Framework-SDK-Syncing-Some-Simple-Data/
This also demonstrates how to attach to an object’s event to see when it has been changed. Though you must keep in mind that Mesh is a Sync system not a real-time data transfer, so it will take a while between data being modified on one machine and then firing the event at the other end.
Okay, so back to the walkthrough… I have lots of machines on my mesh and I want my data to be visible locally (i.e. when I’m offline too). Obviously we want to convert the connect to have an option to local connect so change the Main Method in Program.cs to:
static void Main(string[] args)
{
Start:
LiveItemAccessOptions miao = new LiveItemAccessOptions(true);
try
{
env = new LiveOperatingEnvironment();
string appId = Resources.AppId;
Console.Write("Would you like to try connecting locally (n for cloud)? (y/n)\r\n>");
string yesOrNo = Console.ReadLine();
if (yesOrNo.ToLower() == "y")
{
Console.Clear();
Console.WriteLine("Connecting to the Mesh...");
foreach (string line in meshImage)
{
Console.WriteLine(line);
}
env.ConnectLocal();
}
else
{
Console.Write("Please enter your Email Passport:\r\ne.g. someone@hotmail.com\r\n>");
string address = Console.ReadLine();
Console.Write("Please enter your password:\r\n>");
string password = GetPassword();
Console.Clear();
Console.WriteLine("Connecting to the Mesh...");
foreach (string line in meshImage)
{
Console.WriteLine(line);
}
Uri cloudURL = new Uri(Resources.CloudURI);
NetworkCredential creds = new NetworkCredential(address, password);
env.Connect(creds);
}
Console.Clear();
Console.Write("You are now connected. \r\n");
}
catch (Exception ex)
{
Console.Clear();
Console.WriteLine("Caught exception trying to connect: " + ex.Message);
Console.Write("Would you like to re-enter your password? (y/n)\r\n>");
string yesOrNo = Console.ReadLine();
if (yesOrNo.ToLower() == "y")
goto Start;
else
{
Console.WriteLine("Press any key to exit");
Console.Read();
return;
}
}
HandleUserInput();
}
This will give the user an option to say yes they want to connect locally or no to give their credentials.
Note: I have found that when moving from online to offline (with the local client still connected), it appears that the local client is connected, so you can modify existing MeshObjects, but it timesout when trying to create a new MeshageBoard Object with a 'System.Net.WebException'. when you reconnect to the internet, you may still experience this problem or an authentication issue, and so I believe that it takes a little while for the client to re-authenticate with the cloud.
Next add the option to HandleUserInput:
private static void HandleUserInput()
{
while (true)
{
Console.WriteLine("");
Console.WriteLine("1: Create a New MeshageBoard.");
Console.WriteLine("2: List All MeshageBoards.");
Console.WriteLine("3: Make a Posting.");
Console.WriteLine("4: List All Postings for a MeshageBoard.");
Console.WriteLine("5: Invite Members to a MeshageBoard.");
Console.WriteLine("6: Map a Device.");
Console.WriteLine("7: Reset the demo.");
Console.WriteLine("8: Remove a MeshageBoard");
Console.WriteLine("9: Exit the program.");
Console.Write(">");
string input = Console.ReadLine();
switch (input)
{
case "1":
CreateNewMeshageBoard();
break;
case "2":
ListAllMeshageBoards();
break;
case "3":
MakeAPosting();
break;
case "4":
PrintMessages();
break;
case "5":
InviteMembers();
break;
case "6":
MapDevice();
break;
case "7":
ResetAll();
break;
case "8":
RemoveMeshageBoard();
break;
case "9":
GetOut();
break;
default:
Console.WriteLine("Please enter a valid value.");
break;
}
}
}
and add the MapDevice Method:
public static void MapDevice()
{
string selectedTitle = UserSelectionBoard();
Console.WriteLine(selectedTitle + " was selected. Now select a device map to.");
string selectedDevice = UserSelectionDevice();
if (selectedDevice != null)
{
try
{
MeshageBoardMappings.AddDevice(env, selectedTitle, selectedDevice);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
Console.WriteLine(selectedDevice + " has been mapped to " + selectedTitle);
}
}
I have used a helper method here similar to that which allows the user to select a board and select a contact to select a device called UserSelectionDevice:
// Ask the user to select a specific device with exception handling
private static string UserSelectionDevice()
{
Selection:
ObservableCollection<MeshDeviceResource> meshDevices = GetListOfDevices();
string input = Console.ReadLine();
string selectedTitle = null;
try
{
int sel = Int32.Parse(input);
selectedTitle = meshDevices[sel].Title;
}
catch (FormatException)
{
Console.Write("Please make your selection using the numbers proceeding the title\r\n\r\nTry Again (y/n)?\r\n>");
string yesOrNo = Console.ReadLine();
if (yesOrNo.ToLower() == "y")
{
goto Selection;
}
else
{
return null;
}
}
catch (ArgumentOutOfRangeException)
{
Console.Write("Please select a number from the range given\r\n\r\nTry Again (y/n)?\r\n>");
string yesOrNo = Console.ReadLine();
if (yesOrNo.ToLower() == "y")
{
goto Selection;
}
else
{
return null;
}
}
return selectedTitle;
}
Like the UserSelectionBoard this method uses a method called GetListOfDevices to obtain the list simply iterating over the MeshDevices in the Mesh:
private static ObservableCollection<MeshDeviceResource> GetListOfDevices()
{
ObservableCollection<MeshDeviceResource> meshDevices = new ObservableCollection<MeshDeviceResource>();
meshDevices = MeshageBoardMappings.GetAllDevices(env);
int i = 0;
Console.WriteLine("Choose a Device:");
foreach (MeshDeviceResource resource in meshDevices)
{
Console.WriteLine(i + ": " + resource.Title);
i++;
}
return meshDevices;
}
We want a way to abstract the Mappings and devices from the program so create a new class called MeshageBoardMappings in the class library, add the normal using statements and the public static modifiers to the class:
using Microsoft.LiveFX.Client;
using Microsoft.LiveFX.ResourceModel;
using System.Collections.ObjectModel;
namespace MeshageBoard_Classes
{
public static class MeshageBoardMappings
{
...
Add a method to return a Collection of the MeshDeviceResources:
public static ObservableCollection<MeshDeviceResource> GetAllDevices(LiveOperatingEnvironment env)
{
if (!env.Mesh.Devices.IsLoaded)
env.Mesh.Devices.Load();
ObservableCollection<MeshDeviceResource> meshDevices = new ObservableCollection<MeshDeviceResource>();
foreach (MeshDevice device in env.Mesh.Devices.Entries)
{
meshDevices.Add(device.Resource);
}
return meshDevices;
}
and finally the legwork to add a device to a board:
public static void AddDevice(LiveOperatingEnvironment env, string meshageBoardTitle, string deviceTitle)
{
MeshDevice device = (from i in env.Mesh.Devices.Entries
where String.Equals(i.Resource.Title, deviceTitle)
select i).FirstOrDefault<MeshDevice>();
MeshObject board = (from i in env.Mesh.MeshObjects.Entries
where
String.Equals(i.Resource.Type, "MeshageBoard")
&& String.Equals(i.Resource.Title, meshageBoardTitle)
select i).FirstOrDefault<MeshObject>();
if (device != null && board != null)
{
Mapping mapping = new Mapping();
mapping.Device = device;
board.Mappings.Add(ref mapping);
}
}
We could just as easily send the MeshageBoard and MeshDevice objects through, but this makes it consistant, and LINQ is a great help.
The important point to look for here is that when creating the mapping, the Mapping object has a Device property which references the Device. And we’re done… you can now post to your meshageboards while offline.
Just as a final thought to clean up: modify the CreateMeshageBoard method in MeshageBoard.cs in the class library to catch the System.Net.WebException when trying to create one offline:
public static MeshObject CreateMeshageBoard(LiveOperatingEnvironment env, string BoardTitle)
{
MeshObject messageBoard = new MeshObject(BoardTitle);
messageBoard.Resource.Type = "MeshageBoard";
messageBoard.Resource.Title = BoardTitle;
try
{
env.Mesh.MeshObjects.Add(ref messageBoard);
DataFeed newDataFeed = new DataFeed();
newDataFeed.Resource.Type = "MeshageBoard_DataFeed";
messageBoard.DataFeeds.Add(ref newDataFeed);
messageBoard.Update();
}
catch (System.Net.WebException ex)
{
Console.WriteLine("Unable to create a new MeshObject while offline {0}", ex.Message);
}
return messageBoard;
}
and RemoveMeshageBoard when trying to remove:
public static void RemoveMeshageBoard(LiveOperatingEnvironment env, string BoardTitle)
{
if (!env.Mesh.MeshObjects.IsLoaded)
{
env.Mesh.MeshObjects.Load();
}
MeshObject messageBoard = (from i in env.Mesh.MeshObjects.Entries
where
String.Equals(i.Resource.Type, "MeshageBoard")
&& String.Equals(i.Resource.Title, BoardTitle)
select i).FirstOrDefault<MeshObject>();
Console.Write(BoardTitle + " was selected to be removed.\r\nContinue to delete this board (y/n)?\r\n>");
string yesOrNo = Console.ReadLine();
if (yesOrNo.ToLower() == "y")
{
try
{
env.Mesh.MeshObjects.Remove(messageBoard);
Console.WriteLine(BoardTitle + " removed");
}
catch (Microsoft.LiveFX.Client.LiveOperationException ex)
{
Console.WriteLine("Unable to remove a MeshObject while offline {0}", ex.Message);
}
}
else
{
Console.WriteLine(BoardTitle + " not removed");
}
}
Hope you’re all having as much fun with this as I am…