J2i.Net

Nothing at all and Everything in general.

Using PowerShell to Setup Spectator View

I have some down time and spent some of the time to clear the drive on a computer, reinstall Windows, and rebuild my development environment. While I was doing this I decided to try out SpectatorView for the HoloLens. For those unfamiliar SpectatorView is a solution for creating recordings of what one sees through a HoloLens. The HoloLens does have a recording feature built in, but that feature is low resolution. Using SpectatorView one can produce a high resolution recording. Using a high resolution camera mounted to a HoloLens and a video capture a computer takes the motion data stream from the HoloLens to overlay objects from a HoloLens program onto a video stream. I tried it out last week with a 1K camera (Canon 5D Mark III) and it works great!

One of the personal goals that I have is that when possible to automate setup steps for a development environment, especially if I may need to do them again. I expect to see a fast return on investment for doing this either through time saved when I seed to setup an environment for myself again or when coworkers are able to save time by being able to use the scripts that I've made. Up until now my use of PowerShell has been light, but it looked to be the perfect scripting language for this task. For the most part PowerShell gives access to the COM, WMI, and .Net objects in a scripted environment.

Software and Files Needed for Spectator View

To setup SpectatorView there are a number of software components that are needed.

    • Visual Studio
    • Unity
    • BlackMagic DeckLink SDK
    • Hololens Companion Toolkit
    • Hololens Toolkit
    • OpenCV 3.2
    • Canon SDK (Optional)

Unity and Visual Studio are frequently used within my team, so I'm starting off with the assumption that these two components are already present. Getting the other components is easy enough, so I don't expect an attempt at scripting the acquisition and installations for it to save much time. But I also feel that initial attempts at scripting are better applied to something simple to allow for problems to be found before being applied to more complex scenarios. The Canon SDK can only be downloaded if someone registers with Canon, requests access to the SDK, and then downloads the SDK after receiving approval to download the SDK. Since there are manual steps involved in getting access to the Canon SDK I did not script the acquisition of the file. Similarly the BlackMagic Decklink SDK also requires registration to download. While I could not script the acquisition of these two files I was still able to handle them post download in my script. Each version of these SDKs have a slightly different name since the version number is a part of the file name. To keep the script easy to use it will figure out the actual name of the file when run. If a new version of one of the SDKs were to be used it would only be necessary to replace the ZIP file being used.

Cloning the HoloLend Companion Kit with github

The Hololens Companion Kit is the easiest of the components to acquire through a script. It can be downloaded using GIT. So I won't spend much time talking about how to download it and only mention it at all since it is a necessary component.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";
$companionKitFilePath = $PSScriptRoot + "\HoloLensCompanionKit";
$kitIsDownloaded = Test-Path $companionKitFilePath;
if(!$kitIsDownloaded) {
	git clone $hololensCompanionKitURL;
}

If you are not familiar with what the Test-Path command means don't worry about it just yet. I'll explain it's use when it is used on another component.

File Paths

Leaving nothing to be ambiguous many of the file locations referenced are relative to the location of the power script file. In PowerScript There is a variable named $PSScriptRoot whose value is the full path to the folder from which the script is run. While not absolutely necessary I build paths to various files and folders using this variable.

Downloading OpenCV

For downloading the OpenCV source I've placed the URLs for OpenCV from one of the mirrors in a variable at the top of my script. If I ever wanted to change the version of OpenCV used I would need to change the value in this variable. There are several ways to download a file in PowerScript. I decided to use .Net's WebClient because of speed and predictability. I considered using BITS but when using BITS to download you can't know when the service will get around to downloading the file; it will do so on it's own schedule. Downloading the file with the WebClient is easy. But it provides no feedback while it works. Just so that someone using the script doesn't think that something is wrong I decided to print a message letting them know to hold on for a moment.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";			
$webClient = New-Object System.Net.WebClient;
$openCVArchivePath = $openCVFolder + "\archive.exe"
Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
$webClient.DownloadFile($openCVUrl,$openCVArchivePath );

If you were to take the above and put it in a file with a "ps1" extension and run it you'll find that a file downloads named archive.exe. OpenCV for Windows is distributed in a self extracting archive (Which is why it uses an EXE extension instead of ZIP). Once the files is downloaded if you were to run it you would be greeted with a prompt asking where you want the file to be extracted. For automating the setup I don't want the archive to give these prompts. I'll invoke it passing to it on the command line the location that it should use. Adding those arguments to the above script we end up with the following.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";			
$webClient = New-Object System.Net.WebClient;
$openCVFolder = "${PSScriptRoot}\openCV\openCV3.2"
$openCVArchivePath = $openCVFolder + "\archive.exe"
Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
$webClient.DownloadFile($openCVUrl,$openCVArchivePath );
Write-Host "Download complete";
& "${openCVArchivePath}" -o "${openCVFolder}" -y

This works. But i wanted it to be possible to run this script more than once if it failed for some reason. To prevent the script from reinstalling OpenCV again if it had been installed before I check for the existence of the OpenCV folder. This is a less than thorough test as it would not detect conditions such as an archive being partially unzipped before failing. But this is satisficing for my purposes. The Test-Path< command can be used to determine whether or not a file object exists at some path. I wrapped the above code in a block that checks for the existence of the OpenCV folder first.

$openCVIsDownloaded = Test-Path $openCVFolder
if(!$openCVIsDownloaded) {
	## Download code goes here
}
		

Unpacking the Canon and BlackMagic SDKS

The BlackMagic and Canon SDKs are both in ZIP files. Unpacking them will be about the same. I'll only talk about the Canon SDK for a moment. But the there's equal applicability to what I am about to say to both. Like the OpenCV SDK I'll check to see if the folder for the Canon SDK exists before trying to unpack it. The script requires that the Canon SDK zip file be in the same folder as the script. The Canon SDK versions all start with the same prefix. They all start with EDSDK. To find the file I use the Get-ChildItem command to get a list of files. If there is more than one version fo teh SDK in the folder sorting the results and taking the last should result in the most recent one being selected. PowerShell allows the use of negative index numbers to address an item from the end of a list. Index -1 will be the last item in the list. Taking the last item and getting its FullName value will give the path to the zip file to be unzipped. The Expand-Archive command will unzip the file to a specified path.

################################################
# Unpacking the Canon SDK
################################################
$CanonSDKIsPresent = Test-Path "${PSScriptRoot}\CanonSDK";
if(!$CanonSDKIsPresent)
{
	#Find the Canon SDK Zip(s) present
	$canonSdkArchiveList = Get-ChildItem "${PSScriptRoot}\EDSDK*.zip" | Sort;
	$canonSDKZip = $canonSdkArchiveList[-1].FullName
	Expand-Archive  -path $canonSDKZip -DestinationPath "${PSScriptRoot}\CanonSDK";
} 
################################################
# Unpacking the Black Magic SDK
################################################
$BlackMagicSDKIsPresent = Test-Path "${PSScriptRoot}\BlackMagicSDK";
if(!$BlackMagicSDKIsPresent) 
{
	#Find the Black Magic SDKs present
	$blackMagicSDKList = Get-ChildItem "${PSScriptRoot}\Blackmagic_Decklink_SDK*.zip";
	$blackMagicZip = $blackMagicSDKList[-1].FullName;
	Expand-Archive  -path $blackMagicZip -DestinationPath "${PSScriptRoot}\BlackMagicSDK";
}
	

Modifying the Visual Studio Dependencies File

The Visual Studio project that is part of the SpectatorView software requires some updates so that it knows where the various SDKs are located. The path to the BlackMagic SDK, Canon SDK, and OpenCV software must be added to it. The dependencies file dependencies.props is an XML file. The Common Language Runtime has classes for manipulating XML files. I use one of these to update this file. The exact path of each component could differ depending on which SDK version is used. Rather than hard code the path I use the Get-ChildItem command again to query for the name of folders. For the CanonSDK the line of script code to get the path looks like the following.

$canonPath = (Get-ChildItem "${PSScriptRoot}\CanonSDK")[-1].FullName+"\Windows";

There is a little more nesting that occurs with the other two SDKs. But the lines for getting their paths is similar.

$openCVPath = ((Get-ChildItem (Get-ChildItem "${PSScriptRoot}\opencv" | ?{$_.PsIsContainer} )[-1].FullName)|?{$_.PsIsContainer})[-1].FullName+"\sources\include";
$blackMagicPath = (Get-ChildItem "${PSScriptRoot}\BlackMagicSDK" | ?{$_.PsIsContainer})[-1].FullName +"\Windows";

With those paths populated I now need to load the XML file, update the values, and write them back. The Common Language Runtime's XML class is available to PowerShell. Given a string that contains XML if that string is cast to [xml] it will be parsed and all the nodes available through its properties. To get the XML string from the contents of the dependencies files the Get-Content command will be used. Given a file path it returns the contents as a string.

$dependencies = [xml] (Get-Content -Path "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props");
$dependencies.Project.PropertyGroup[0]."OpenCV_vc14" = $openCVPath ;
$dependencies.Project.PropertyGroup[0]."DeckLink_inc" = $blackMagicPAth;
$dependencies.Project.PropertyGroup[0]."Canon_SDK" = $canonPath;
$dependencies.Save( "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props" );

Next Steps

The script is at the bottom of this post. Running this script doesn't result in SpectatorView being 100% setup. It is still necessary to go through calibration (a step that requires you to physically do some things) in front of the camera) and copying the libraries from the sample project into your own project. There are opportunities for further automating the setup. But I felt this was a good time to write about what is working at this moment (if I wait until it is perfect ot may never get posted).

clear;
################################################
# A few download URLs
################################################
$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";
$holoLensCompanionKitURL = "https://github.com/Microsoft/HoloLensCompanionKit";
$webClient = New-Object System.Net.WebClient;
Write-Host "Running from ${PSScriptRoot}";


$companionKitFilePath = $PSScriptRoot + "\HoloLensCompanionKit";
$openCVFolder = "${PSScriptRoot}\openCV\openCV3.2"
$openCVArchivePath = $openCVFolder + "\archive.exe"

$kitIsDownloaded = Test-Path $companionKitFilePath;
if(!$kitIsDownloaded) {
	git clone $hololensCompanionKitURL;
}

$openCVIsDownloaded = Test-Path $openCVFolder
if(!$openCVIsDownloaded) {
	New-Item "$openCVFolder"  -type directory
	Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
	$webClient.DownloadFile($openCVUrl,$openCVArchivePath );	
	Write-Host "Download complete";
	& "${openCVArchivePath}" -o "${openCVFolder}" -y
}
################################################
# Unpacking the Black Magic SDK
################################################
$BlackMagicSDKIsPresent = Test-Path "${PSScriptRoot}\BlackMagicSDK";
if(!$BlackMagicSDKIsPresent) 
{
	#Find the Black Magic SDKs present
	$blackMagicSDKList = Get-ChildItem "${PSScriptRoot}\Blackmagic_Decklink_SDK*.zip";
	$blackMagicZip = $blackMagicSDKList[-1].FullName;
	Expand-Archive  -path $blackMagicZip -DestinationPath "${PSScriptRoot}\BlackMagicSDK";
}
################################################
# Unpacking the Canon SDK
################################################
$CanonSDKIsPresent = Test-Path "${PSScriptRoot}\CanonSDK";
if(!$CanonSDKIsPresent)
{
	#Find the Canon SDK Zip(s) present
	$canonSdkArchiveList = Get-ChildItem "${PSScriptRoot}\EDSDK*.zip" | Sort;
	$canonSDKZip = $canonSdkArchiveList[-1].FullName;
	Expand-Archive  -path $canonSDKZip -DestinationPath "${PSScriptRoot}\CanonSDK";
} 
else 
{
	Write-Host "Canon SDK already present";
}
################################################
# Modifying the Dependencies File
################################################
$canonPath = (Get-ChildItem "${PSScriptRoot}\CanonSDK")[-1].FullName+"\Windows";
$openCVPath = ((Get-ChildItem (Get-ChildItem "${PSScriptRoot}\opencv" | ?{$_.PsIsContainer} )[-1].FullName)|?{$_.PsIsContainer})[-1].FullName+"\sources\include";
$blackMagicPath = (Get-ChildItem "${PSScriptRoot}\BlackMagicSDK" | ?{$_.PsIsContainer})[-1].FullName +"\Windows";

$dependencies = [xml] (Get-Content -Path "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props");
$dependencies.Project.PropertyGroup[0]."OpenCV_vc14" = $openCVPath ;
$dependencies.Project.PropertyGroup[0]."DeckLink_inc" = $blackMagicPAth;
$dependencies.Project.PropertyGroup[0].= $canonPath;
$dependencies.Save( "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props" );

Resolving Problems Connecting to the Gear S2/S3 for Development

On occasions I develop for the Gear S2/S3 watches from Samsung. (From a development perspective these watches are nearly identical, so I will collectively refer to them as the Gear S watches). When returning to develop after a period away from them there are a few mistakes that I find I sometimes make. Looking in some support forums I see there are others that make these mistakes too. To both help out others that run into this (and as a note to myself) I've made this post to cover some of the checks necessary. 

  1. Ensure Debugging is Enabled
  2. Ensure Wifi is Always Enabled
  3. Check the watch's IP address
  4. Ensure the watch is unlocked
  5. Connect to the watch from SDB 
  6. Redeploy the development certificate

 

Ensure Debugging is Enabled

Before anything else will work debugging must be enabled on the watch. This setting will be cleared if you've done a hard reset on the watch or if you have connected it to a different phone. You can change the setting by navigating to Settings ➜ Gear Info ➜ Debugging and ensure that the setting is checked. 

Ensure WiFi is always enabled

You'll want to have WiFi set to always on. If you have it set to "Auto" you might not be able to connect. If it is set to "Off" then you will invariably will not be able to connect. Setting WiFi to "Always On" will cause the battery to drain excessively. When developing you'll want to have the charging cradle close by. To set WiFi to always be on navigate to Settings ➜ Connections ➜ WiFi ➜WiFi and select "Always On."

 

Check the Watch's IP Address

You need to know the watch's IP address to attach to it for debugging and deployment. Remember that the IP address will be different if you go to a different wireless network or could be different if you reconnect to the same network.  To see the watch's IP address navigate to Settings ➜ Connections ➜ Wi-Fi ➜ Wi-Fi networks ➜ select your network ➜ scroll down to the IP address.

Ensure the Watch is Unlocked

The watch must be unlocked for the initial connection. While this may be obvious what is less obvious is how quickly the watch can become locked again. The heart rate monitor on the back of the watch also acts as a presence detection sensor; the watch is aware of when it's been removed from your wrist and will go into a locked state almost immediately if you have a lock code/patter on it. When handling the watch if your finger passes over this sensor the watch may lock. You could unlock the watch, set it down in the cradle, and it could be locked again because of your finger coming close to the sensor. 

Heart Rate Monitor on the back of the Gear S2

Connect to the Watch using SDB

Before opening Tizen Studioconnect to the watch using SDB. From the command line on your computer (or Terminal if you are on a Mac) navigate to the folder that contains Tizen Studio and then into the tools folder inside of it. Type the following substituting your own IP address here. 

sdb connect 192.168.1.181

If this is the first time the watch has connected to the machine from which you are typing the command the watch will prompt you to accept an RSA key. If you don't accept it the connection attempt will fail. Sometimes when you attempt to connect the command line tool will print a failure message the first time even though it has actually connected. Run the command a second time and you'll get a message that the watch is already connected. 

Redeploy your Development Certificate

You only need to do this if the watch has been reset since the last time you've done development on it (or if you've never developed on the watch before).  Certificate management is a topic of it's own; I won't go into it here. Provided that you have a handle on development certificates the above should be enough to get your watch connected to your computer for development. 

 

Working Around the Missing Real Time Clock in Windows IoT

I've got a project planned involving Windows IoT for which I need the system to have the correct time. The Raspberry Pi with Windows IoT has no real time clock. It initializes the time over NTP when connected to a network. But when not connected to a network the time will be wrong.   It has no support for a real time clock at this point in time. That's no good. I searched for how people have worked around this. A frequent solution was to add a real time IC and have ones solution communicate directly with the real time chip and ignore the native APIs built around time. I don't like this solution. I'd like to maintain compatibility with other systems and not make something that is dependent on a specific implementation of a clock. I wanted a way to get the Raspberry Pi to initialize from the RTC instead of NTP. 

It took a while to create a solution for this because the APIs needed to manipulate the system time are not available to UWP applications. I managed to create a solution with a power shell script, a program that I made, and a real time clock. The complete solution is available at CodeProject.com. Here is a summary of the solution. The stand alone application I made only does two things. It can read the time from the real time clock chip that I used and print it out as a string. It can also look at the system time and set the time in the real time clock to match. I use the latter of these two capabilities to set the time on the real time clock. When ever the system boots up the first of those capabilities is used to expose the current time in the RTC  to the Power Shell environment. From there I can use the Set-Date command to update the system time. I've saved a Power Shell script to run every time the system turns on to do just this. Now when I turn on my Raspberry Pi off network within a few seconds of turning it on it now has the right time. 😊

ViewModelBase and DelegateCommands in Sample Code

I'm working on some sample code for some upcoming blog post. There are two classes that are used throughout the examples and will probably be used in future samples. I wanted to mention them here for future blog post. This is applicable to both UWP and WPF projects. The first is the base class that I use for all of my ViewModel classes. 

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Threading;


namespace Common
{
    public class ViewModelBase
    {

        public static System.Threading.SynchronizationContext SyncContext;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                SendOrPostCallback  a = (o) => { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); };
                if (SyncContext == null)
                    a(null);
                else
                    SyncContext.Send(a, null);
                
            }
        }

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

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

The field SyncContext is needed for code that runs asynchronously on another thread (for WPF projects this is a Dispatcher instead). If the code attached to PropertyChanged interacts with the UI an exception will occur if this interaction happens on a different thread than the one on which the UI controls were created. When the OnPropertyChanged method is called the SyncContext is used to marshal control back to the UI thread.  One of the OnPropertyChanged methods takes as an argument an expression. I prefer to use this when passing the name of the field being updated to the OnPropertyChange handler because it provides the advantage of compile checking for typos in the name and will be updated if the Rename command is used on a property.  The other frequently used class(es) is the DelegateCommand

using System;
using System.Windows.Input;

namespace Common
{
    public class DelegateCommand : ICommand
    {
        public DelegateCommand(Action execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action execute, Func canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute != null)
                return _canExecute();

            return true;
        }

        public void Execute(object parameter)
        {
            _execute();
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }

        public event EventHandler CanExecuteChanged;

        private Action _execute;
        private Func _canExecute;
    }

    public class DelegateCommand<T> : ICommand
    {
        public DelegateCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute != null)
            {

                return _canExecute((T)parameter);
            }

            return true;
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }

        public event EventHandler CanExecuteChanged;

        private Action<T> _execute;
        private Func<T, bool> _canExecute;
    }
}

The DelegateCommand class is used to make commands that can be bound to a button. This allows us to use DataBinding to associate code with a button through data binding. 

First Look at the Microsoft HoloLens

I've had a chance to have some quality time with the Microsoft HoloLens thanks to the Emerging Experiences team. It's an interesting device. Despite the name it doesn't have anything to do with holography. Rather the name seems to be inspired by a metaphor for the experience of using it. Having gotten a chance to do some development I've started writing about my experiences. I wrote in expanded form a blog post published on the Emerging Experiences blog about the history of stereoscopic devices.  I've also wrote about the making of a very simple game-like experience in an article posted on CodeProject.com. More articles are to follow. 

Fallout 4 Pipboy Traffic Logging in C#

Download Code (2.88 mb)

Bethseda Games released Fallout 4 with an accessory application known as Pip-boy. Within the game the main character wears a computing device on the wrist that gives information relevant to what's going on. Current location, objectives, inventory, status, and other information is available through this device. The Pip-boy application communicates with the game to show the same information on one's mobile device. Being able to have this information accessible while playing the game can be a lot more convenient than the alternative; pausing the game to view the same information. 

If you look around on the Internet you can find a couple of sites that talk about the protocol that the game uses to communicate with the application. It appears that creating one's own client for the game isn't difficult. I've got some other ideas on accessory applications that can be made. Rather than test the application against a live network stream initially I wanted to grab the traffic from the Pip-boy application and save it to a file (this makes debugging much easier).  So I made a quick console application that does just this.  I came across information on the communication between the Pip-boy and the game console in a blog entry by Kyle Kelley named "Fallout 4 Service Discovery and Relay"I'd encourage you to go read the entry to see how he figured this out. 

To summarize what's going on there is communication occurring over two channels. For discovery the game console is listening for UDP broadcast messages on port 28000. A client wishing to discover the game console only need broadcast a message with a small JSON payload to get a response back from the machine that host the game. A relay application will both need to request the address of the real game console and also be prepared to respond to the same type of broadcast request so that a real Pip-boy client can discover it. The relay application also needs to make sure it's not discovering itself. If the relay application starts listening for broadcast before making it's own to find the real game machine then it will receive a copy of it's own request. This can be easily remedied by having the relay application not respond to any discovery requests until after it has already found the game machine. While I tested this and found it to work another potential solution is to include additional information in the JSON message that can be used to recognize request that originated on the relay server. The code for making the relay discoverable to the Pip-boy follows.

 

//prepare to respond to incoming discovery requests
UdpClient discoveryListener = new UdpClient(PIP_AUTODISCOVER);
discoveryListener.EnableBroadcast = true;
discoveryListener.BeginReceive(DiscoverRequestReceived, discoveryListener);

When a Pip-boy client broadcast it's request the DiscoverRequestReceived method runs.

const int PIP_AUTODISCOVER = 28000;

static void DiscoverRequestReceived(IAsyncResult result)
{
    //this request is from a remote client. Send back an acknowledgement
    UdpClient discoveryListener = (UdpClient)result.AsyncState;
    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

    //If the request came from the machine on which this program is hosted it could
    //be from this very same program! This could result in the program connecting
    //with itself if we don't have a strategy for handling it. For my strategy I will
    //just add something extra to the JSON data that this program can recognize. If
    //you'd like to use a different strategy uncomment the following code if you need
    //to recognize that the request came from the same machine. 
    bool requestFromHostMaching = false;
    var hostEntry = Dns.GetHostByName(Dns.GetHostName());
    var matchingAddress = hostEntry.AddressList.Where((x) => x.Equals(ep.Address)).FirstOrDefault();
    if (matchingAddress != null)
        requestFromHostMaching = true;

    //Parse the response and make sure it appears to be valid
    byte[] data = discoveryListener.EndReceive(result, ref ep);
    string dataString = Encoding.UTF8.GetString(data);
    DiscoverRequest response = (DiscoverRequest)JsonConvert.DeserializeObject(dataString, typeof(DiscoverRequest));
    //If the response is invalid do nothing further. 
    //if the request came from this program ignore it
    if ((response == null) || (response.IsPassthrough))
    {

        discoveryListener.BeginReceive(DiscoverRequestReceived, discoveryListener);
        return;
    }

    Console.Out.WriteLineAsync(String.Format("Request payload: {0}", dataString));

    //Note, the Passthrough member is not part of the request that comes from 
    //the game console. I've added it so that I can recognize requests from my
    //own machine. 
    byte[] responseBytes = StringToBytes(JsonConvert.SerializeObject(new DiscoveryResponse{ IsBusy = isBusy, MachineType = "PC", IsPassthrough = true }));
    discoveryListener.Send(responseBytes, responseBytes.Length, ep);
    Console.Out.WriteLine("[{0}]Discover request received from {1}", DateTime.Now, ep.Address);

    //Listen for next request
    discoveryListener.BeginReceive(DiscoverRequestReceived, discoveryListener);
}

public class DiscoverRequest
{
    public DiscoverRequest()
    {
        IsPassthrough = false;
    }
    public string cmd { get; set; }
    public bool IsPassthrough { get; set; }
}
public class DiscoveryResponse
{
    public DiscoveryResponse()
    {
        IsPassthrough = false;
    }
    public bool IsBusy { get; set; }
    public string MachineType { get; set; }
    public bool IsPassthrough { get; set; }

}

Now that the Pip-boy can discover the relay we still need for the relay to discover the real game console.

//discover the real service
UdpClient client = new UdpClient();
var discoverMessage = JsonConvert.SerializeObject(new DiscoverRequest { cmd = "autodiscover", IsPassthrough = true });

byte[] sendbuf = Encoding.ASCII.GetBytes(discoverMessage);
byte[] recvBuffer = new byte[128];
IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, PIP_AUTODISCOVER);

client.Send(sendbuf, sendbuf.Length, ep);
recvBuffer = client.Receive(ref ep);
var response = Encoding.ASCII.GetString(recvBuffer);
var responseMessage = JsonConvert.DeserializeObject(response);
Console.WriteLine(response);
Console.Out.WriteLine("[{0}] Game instance found at {1}", DateTime.Now, ep.Address);

With discovery handled all that's left is to ferry the bytes back and forth between the Pip-boy and the game console; a copy of the bytes from the game console will also be written to a file. All the data that we care about will be communicated with TCP/IP. The .Net library allows us to interact with a TCP client as a stream; many of the same methods that one might use for interacting with a file stream can be applied here. The transfer of the bytes is simple. Read some number of bytes from a source. Write those same bytes to a destination and if desired write another copy of those bytes to a file stream.

async static void TransferData(TcpClient sourceClient, TcpClient destinationClient,  Stream saveStream = null)
{
    if (isClosing)
        return;
    try
    {
        var sourceStream = sourceClient.GetStream();
        var destinationStream = destinationClient.GetStream();
        if (sourceStream.CanRead && destinationStream.CanWrite)
        {
            byte[] readBuffer = new byte[1024];
            int bytesRead = await sourceStream.ReadAsync(readBuffer, 0, readBuffer.Length);
            if (bytesRead > 0)
            {
                await destinationStream.WriteAsync(readBuffer, 0, bytesRead);
                if (saveStream != null)
                {
                    await saveStream.WriteAsync(readBuffer, 0, bytesRead);
                    await saveStream.FlushAsync();
                }
            }
            TransferData(sourceClient, destinationClient, saveStream);

        }
    }
    catch (IOException)
    {
        Console.Out.WriteLine("Closing Session");
        isClosing = true;
        sourceClient.Close();
        destinationClient.Close();
    }
}

In the last line of this method it appears to call itself. Whether or not this is recursion I'll leave open to argument. This call will never result in stack space being exhausted. With TransferData being marked as asynchronous a call to this method doesn't result in a call that takes execution a level deeper into the stack. Instead it schedules the execution of this method in the thread pool. The optional last parameter to this method is a stream to which a copy of the data is to be saved. For my purposes I only wanted to save data that was coming from the game console. I provide a file stream for one call to the TransferData method and not another.

TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Any, PIP_CLIENT));
listener.Start();
while (true)
{
    TcpClient remoteClient = listener.AcceptTcpClient();
    isBusy = true;
    ep.Port = PIP_CLIENT;
    TcpClient pipClient = new TcpClient(ep.Address.ToString(), PIP_CLIENT);
    
    FileStream gameMessageCaptureStream = new FileStream("capture.data", FileMode.Create);
    TransferData(pipClient, remoteClient,  gameMessageCaptureStream);
    TransferData(remoteClient, pipClient);

    Console.Out.WriteLine("[{0}] Connection accepted from {1}", DateTime.Now, remoteClient.Client.RemoteEndPoint);

    while (!isClosing)
        Thread.Sleep(100);
    isBusy = true;
    gameMessageCaptureStream.Close();

    Thread.Sleep(50);
    isClosing = false;

}

That's all the code that I used. Now that I've got a saved data stream I've managed to successfully parse all the data that the Pip-boy sends back and forth. I'm still working on understanding it right now.  Once I understand it I hope to make a client in C# for other purposes. 

 

 

Tizen based Speedometer

Just before heading off to the Samsung Developer's conference I had a chance to implement a Tizen based speedometer for the Gear. The Galaxy Gear and Galaxy Gear 2 don't have hardware for determining the user's location or speed. To make a usable application a second executable was needed that would run on the user's phone to use it's location hardware and send the information back to the watch over Bluetooth. The full details of how I did this have been documented and posted on CodeProject.com. You can find the details here

While at the conference I managed to snag a Galaxy Gear S (or three) and decided to modify the application to run as a standalong application on the Gear S. While I was successful at doing this, my general advice is against doing so unless no other method is necessary. There are two reasons I say this. The first is that the Gear S would not generate location/speed updates at a frequency that I considered acceptable (they could be as close together as 2 seconds but seemed to typically be around 10 seconds apart). The phone's location hardware seemed to generate updates about once per second. The second reason I advise against this is to give priority on protecting the watches's battery over the phone's. Given a full charge and light use the phone can last several days on it's battery (and much longer on conservative use). The watch does not have the potential of being able to last nearly as long as the phone. Nevertheless there are still occasions where using the phone's hardware may be desirable; such as when the phone is left behind or location services are disabled and not available on the phone. 

The Tizen Wearable SDK allows the development on Tizen based applications using HTML, JavaScript, and CSS. There are some Tizen specific classes available in JavaScript for accessing hardware specific features (ex: power management), but most of the rest of what is available is based on web standards. A user's speed and location can be acquired using the W3C Geolocation API Specification. There also exists a library called the Tizen Advanced UI (TAU) framework that's useful for constructing UI components and interactions. 

Constructing the UI

The UI is built in HTML. Most of the elements that make up the UI will make sense if you are experienced at editing HTML. There are a few points that have meaning specific to the TAU framework. Throughout the UI some of the CSS classes are associated with TAU behaviours. These include

 

  • ui-page - the contents of this element for a virtual page. Only one virtual page is visible at a time even though there may be several virtual pages in the same HTML file.
  • ui-page-active - marks the virtual page that is currently displayed
  • ui-header - An element with this style will appear at the top of the display
  • ui-footer - an element with this style will appear at the bottom of the page
There are other UI styles that come with the TAU framework (look in tau.js in the source code). The classes ui-page and ui-page-active are most relevant to navigation within the application. Navigation occurs by changing which element has the ui-page-active class. Only one virtual page will have this class set at any given time. The TAU framework will also take care of manipulation of these classes. Navigation can be implemented through what appears to be regular anchor tags. The href attribute for the anchor is set to the ID of the virtual page to which we want to navigate with a # before the name.  The entirity of the contents for the <body> element of the file that forms this application is below (I've omitted the header which contains the CSS and JS files that get included into the interface). 

 

 

 <div class="ui-page ui-page-active" id="main">
 	<div class="ui-header" data-position="fixed">
 		<h2 class="ui-title">Speedometer</h2>
 	</div>	 	 	
 	<div class="content" style='margin:auto;text-align:center;vertical-align:center;height:200px'>
	  	<div style='margin:auto;'  style="height:80%;">
	  		<div  id="speedText">...</div>
	  		<div  id="speedUnitText" ></div>
	  		<div id="statusText"></div>
	  	</div>  
 	</div>
 	<div class="ui-footer appBar">
 		<a href="#settings" data-transition="slideup">
 			<img id="startTracking" src="images/dark/gear.png" class="appBarItem"  />	 			
 		</a>	 		
 	</div>
 </div>	 
 
<div id="settings" class="ui-page">
 	<div class="ui-header" data-position="fixed">
 		<h2 class="ui-title">Settings</h2>
 	</div>
 	<div class="content">
 		<ul class="ui-listview" id="unitsList">	 			
 		</ul>
 		<input type="checkbox" id="keepScreenOn" name="keepScreenOn" />
 		<label style="font-size:1.25em" for="keepScreenOn" onclick="setPowerPolicy()">Keep Screen On</label> 
 	</div>	 	
 </div>

For this application there are only two virtual pages. One is the screen that displays speed. The other is the settings screen. 

Acquiring Speed

Speed information is inexorably linked to location information. To get a user's speed we must request their location. Depending on the resolution of the location information speed information may or may no be available within it (you may receive a location without speed, but never a speed without location). To request location information the geolocation object within the navigator object is used. This method can be used in many other browsers, though if geolocation information is not supported on the browser this object will return null. The object has several methods that can be used to retrieve location information. The one we want to use is called watchLocation. It will begin the cycle of retrieving several location updates until the geolocation object is asked to stop watching. The watchLocation method takes as it's arguments a reference to the method that we want to use to receive location information, a reference to the method that should be called when an error occurs, and optionally an additional parameter that may contain additional information on timeouts or the maximum age of location information. 

function startWatch() {
	if(locationWatchId === null) {
		console.log('starting location watch');
		locationWatchId  = navigator.geolocation.watchPosition(locationUpdate, locationUpdateError, {maximumAge:250})
		console.log('watch id:' + locationWatchId);
	}
}

function locationUpdate( position ) {
	console.log('position update:' + position.toString() + ':' + (new Date()));
	lastPosition = position;
	var currentSpeed = lastPosition.coords.speed;
	hasLocation = true; 
	$('#speedText').html(formatSpeed(currentSpeed));
	$('#speedUnitText').html(currentUnit.label);
	$('#speedText').css('color', '#fff');
}

When I receive the location information I'm using jQuery to display the updated information on the screen. Speed information is received in meters per second. The formatSpeed method is a method that I've made to convert the speed to either miles per hour or kilometers per hour.

Keeping the Screen On

The default power settings on the Galaxy Gear S keep it fairly prompt about turning the screen off. For the speedometer application one might want to keep the screen on. I've included a checkbox in the settings screen that allows the screen to be kept turned on. One of the tizen specific calls is the ability to control the power state of the device. When the checkbox is set I request that the minimum power state of the device to be set to SCREEN_NORMAL. This is sufficient for keeping the device running and the screen on. If the checkbox is cleared I release the request.

function applyPowerPolicy() {
	if(!tizen) { return; }
	var screenPolicy = localStorage.getItem('screenPower');
	if (screenPolicy === null) {
		screenPolicy = 'SCREEN_NORMAL';		
	}
	
	console.log(screenPolicy);
	if(screenPolicy === "SCREEN_NORMAL") 
		{ tizen.power.request("SCREEN", "SCREEN_NORMAL"); }
	else
		{ tizen.power.release("SCREEN"); }
}

function setPowerPolicy() {	
	var isChecked = $("keepScreenOn").is(':checked');
	var policy = isChecked ? "SCREEN_NORMAL" : "";
	localStorage.setItem('screenPower', policy);
	applyPowerPolicy();
}

Other Implementation Details

The other details of how the standalone Speedometer application works can be found in the CodeProject article. Be sure to check it out. I've opened a Samsung Seller application and plan to post the application in their store once it passes certification. I plan to write about my experience once I get through the process.

Tizen and the Samsung Developer's Conference

I've had the original Galaxy Gear since last year. When it was first released it used Android as it's operating system (And not Android Wear, that wasn't available at the time). Earlier this year Samsung decided to make Tizen the main operating system of it's watches. Tizen is an operating system derived from Samsung's Bada and some other efforts that Intel and others had underway.

The current climate in which it is being released is interesting. Google has released Android TV as their solution for the living room. Samsung and Apple have both released car solutions. Some televisions already suuort a series of applications for connecting to services, and the most dominant mobile operating systems are Android and iOS. In this environment Samsung is releasing an operating system to compete for market share across all of these fronts; the living room, car infotainment systems, portable mobile devices, set-top boxes, and more are the target device types that Tizen is aimed for. I might be pessimistic were it another company releasing a new operating system but I'm expecting better outcome from Samsung.

This week Samsung is releasing the Galaxy Gear S in the USA. This is the first Tizen devices with data connectivity. A few days later from the 11-13 November at the Moscone Conference Center (San Francisco) Samsung is hosting a developers conference covering Tizen and development for the Watch and the Tizen powered Televisions (among other topics, such as the SmartThings platform). I plan to be there. I've already started to look into Tizen and made my first Hello World program of types. The details of it can be viewed over at The Code Project.

Tizen Speedometer

From working with this first project and seeing the number of web standards that the HTML side of the development story is based on I can see it seems to be an easy platform to jump into. Those that have already been doing mobile application development with HTML will be able to start development with little effort compared to starting other platforms. Getting started is easy. Though some amount of additional effort will be needed when thinking of how tasks are divided between a phone application and a watch application; applications on the Gear S can run independently of the phone or in conjunction with an application on the phone.

Astrophotography with Windows Phone Part 1: The Hardware Interface

Warning in this post I'm interfacing my telescope to a custom circuit. While I am totally comfortable doing this myself do so at your own risk. The CGEM mounts sell for well over 1,000 USD. If you try to follow what I did and burn out your telescope's mout you are on your own. 

If you haven't already I encourage you to check out my previous post to get the full context of what this post is about. To summarize I'm making a solution for controlling my telescope from my Windows Phone and this post describes the hardware interface.  The phone is going to communicate with the telescope over Bluetooth. The Bluetooth tranceiver from the post on controlling the car with the phone is being repurposed in this post. I'm constructing this circuit on a breadboard. If you would like to follow along with this project but don't want to assemble a bluetooth/serial adapter yourself a fully assembled circuit can be purchased at online retailers such as Amazon.com for 50-80 USD. I've not used these receivers myself and can't provide any information on changing settings on them.

For those familiar with my telescope you may also be aware of a Wi-Fi adapter sold under the name SkyQ Link. Why am I not using it? The reason is simple, there is no information available on interacting with the SkyQ. It seems to be exclusively used by Celestron applications. 

The telescope uses a serial communications standard known as RS-232 for communicating with a computer. The SkyQ Link, GPS receiver, and other accessories communicate with the telescope's mount through one of it's many serial ports.Not all of these serial ports are the same and use different voltage levels. The 6 conductor RJ-12 jacks tend to use TTL voltage levels. The jack on the bottom of the telescope's hand controller uses RS-232.  The RS-232 standard (Created in the 1960s) defines voltages levels, timing, and some other details. To convert the output from the bluetooth tranceiver to levels that the telescop's serial port expects the MAX 232 chip is being used. Celestron ships a 4 conductor RJ-22 to 9-pin D cable that comes with the telescope for interfacing to a computer. This is the connection that we'll be using. 

The circuit is composed of 3 major components; the Bluetooth transceiver, the RS-232 adapter, and a power regulator. The power regulator is somewhat optional. The other two components will run off of any voltage between 3.3 volts and 5 volts. So 3 battery cells would meet needs. But by using a power supply I'm opening up the possibility of other power supplies. The power regulator (known as a 7805 regulator) produces an output voltage that is at least 1.7 volts lower than the input, but not more than 5 volts. If it receives a 5 volt input the 1.7 volt drop will put the output at 3.3 volts which is within toleration of what it needed. If the input voltage is 6.7 volts or higher the 7805 outputs 5 volts and burns off the extra energy as heat. In general you can put up to 12 volts through the 7805. Higher voltages are possible, but they may require a heat sink or additional cooling to prevent overheating. 

 

Powered by a cell phone charger that I received the last time I met with the Windows Phone team at Microsoft (Thanks Desiree!). Because of the voltage regulator there are many other power source options that you could use such as a 9 volt battery. With the extremely simple hardware construction done we can start the first part of communication with the telescope. For now we are only going to be concerned with retrieving state information from the telescope. 

To make sure the circit works I wrote some throw-away code to communicate with the telescope mount. This code is not going to tell the mount to do anything; there's some math that we need to cover before we start to do that. Instead we will query the telescope's current state. I'll ask it for the time, whether or not it's aligned, and the model number. I've made an enumerated type to represent commands. These commands can be queued up to be executed. I also made a few bindable properties to hold the state information.

public enum Command
{
    Nop,
    GetModel,
    IsAlignmentComplete,
    IsGotoInProgress,
    GetVersion,
    GetYear,
    GetDate,
    GetTime,
    GetLongitude,
    GetLatitude,            
};

Command _currentCommand = Command.Nop;
Queue _commandQueue = new Queue();

DateTimeOffset _mountDate = DateTimeOffset.MinValue;
public DateTimeOffset MountDate
{
    get { return _mountDate; }
    set
    {
        if(value!=_mountDate)
        {
            _mountDate = value;
            RaisePropertyChanged("MountDate");
        }
    }
}

string _mountModel;
public string MountModel
{
    get { return _mountModel; }
    set
    {
        if(_mountModel!=value)
        {
            _mountModel = value;
            RaisePropertyChanged("MountModel");
        }
    }
}

bool _isAlignmentComplete;
public bool IsAlignmentComplete
{
    get { return _isAlignmentComplete; }
    set
    {
        if(_isAlignmentComplete!=value)
        {
            _isAlignmentComplete = value;
            RaisePropertyChanged("IsAlignmentComplete");
        }
    }
}

These commands are dequeued one at a time, sent to the telescope, the response is processed, then on to the next command.

void SendNextCommand()
{

    if(_commandQueue.Count>0)
    {
        _currentCommand= _commandQueue.Dequeue();
        switch (_currentCommand)
        {
            case Command.GetModel:
                Write("m");
                break;
            case Command.GetTime:
                Write("h");
                break;
            case Command.IsAlignmentComplete: Write("J");
                break;
        }
    }
}

The telescope doesn't necessarily send the response back in a single chunk. This means that I will need to accumulate the responses until I have enough to assemble a complete response from the parts received.

async private void WaitForData(StreamSocket socket)
{
    try
    {
        byte[] bytes = new byte[128];
        await socket.InputStream.ReadAsync(bytes.AsBuffer(), (uint)bytes.Length, InputStreamOptions.Partial);
        bytes = bytes.TakeWhile((v, index) => bytes.Skip(index).Any(w => w != 0x00)).ToArray();
        System.Buffer.BlockCopy(
            bytes, 0, _responseBuffer, _responseBufferPosition, 
            Math.Min(bytes.Length, _responseBuffer.Length-_responseBufferPosition)
        );
        _responseBufferPosition += bytes.Length;
                
        //If we wanted to do any preprocessing with the message this is the place to do it. 
        //I've currently got no preprocessing needs, so I'm just passing it on to whoever
        //may be listening. 
        OnMessageReceived(bytes);
        ProcessResponse();
    }
    catch
    {
    }
    finally
    {
        WaitForData(socket);
    }
}

void ProcessResponse()
{
    byte delimiter  = 0x23;//#
    int delimiterPosition = -1;
    for(int i=0;i<_responseBufferPosition;++i)
    {
        if (_responseBuffer[i] == (byte)delimiter)
        {
            delimiterPosition = i;
            break;
        }
    }
    if(delimiterPosition>-1)
    {
        //copy out the message
        byte[] response = new byte[delimiterPosition];
        System.Buffer.BlockCopy(_responseBuffer,0,response,0,delimiterPosition);
        //shift the data in the response buffer
        System.Buffer.BlockCopy(_responseBuffer,delimiterPosition+1,_responseBuffer,0,_responseBuffer.Length -( delimiterPosition+1));
        _responseBufferPosition -= delimiterPosition+1;

        switch(_currentCommand)
        {
            case Command.GetModel: ProcessGetModel(response);break;
            case Command.IsAlignmentComplete: ProcessIsAlignmentComplete(response); break;
            case Command.GetTime: ProcessGetTime(response); break;
            default: break;
        }
        SendNextCommand();
    }
}

A different method is defined to process each type of response that comes back. I originally thought about returning the responses as strings. But many of the responses are not composed of printable characters. So I've left the responses as byte arrays.

void ProcessGetModel(byte[] response)
{
    if(response.Length==1)
    {
        switch(response[0])
        {
            case 1: MountModel = "GPS Series"; break;
            case 3: MountModel = "i-Series"; break;
            case 4: MountModel = "i-Series SE"; break;
            case 5: MountModel = "CGE"; break;
            case 6: MountModel = "Advanced GT"; break;
            case 7: MountModel = "SLT"; break;
            case 9: MountModel = "CPC"; break;
            case 10: MountModel = "GT"; break;
            case 11: MountModel = "4/5 SE"; break;
            case 12: MountModel = "6/8 SE"; break;
            case 14: MountModel = "CGEM?"; break; //Need to find documentation to back this up
        }
    }
}
void ProcessIsAlignmentComplete(byte[] response)
{
    if(response.Length==1)
    {
        IsAlignmentComplete = (response[0] > 0);
    }
    else
    {
        IsAlignmentComplete = false;
    }
}

void ProcessGetTime(byte[] response)
{
    if((response.Length==8))
    {
        int hour = response[0];
        int minutes = response[1];
        int seconds = response[2];
        int month = response[3];
        int day = response[4];
        int year = 2000 + response[5];
        int gmtOffset = (response[6] < 128) ? response[6] : 256 - response[6];
        bool daylightSavings = (response[7] != 0);

        DateTimeOffset dateTime = new DateTimeOffset(
            year, month, day, hour, minutes, seconds,  TimeSpan.FromHours(gmtOffset + (daylightSavings?-1:0))
         );
        MountDate = dateTime;

    }
}

I started a program that queued up and executed commands. The responses were pleasing.  When I first started the program I had forgotten to align the telescope. I was trying to figure out what was wrong before I realized the response was correct. I aligned the telescope and started to get back the expected response. The date returned was also correct. For the telescope mount model I got back an unexpected result. For my telescope the response was the byte value 14. The protocol document tha tI have from Celestron doesn't list a value for 14. But the document that I've got also doesn't mention my model. So it appears the document simply has not been updated to account for my telescope. This is fine and an important learning experience; it's possible that the code could one day run against a telescope mount that hasn't yet been created. 

Now that I've successfully been able to communicate with the telescope mount it's time to talk about the math behind what needs to be done. I'll detail most of the math in my next entry.  If you want to follow along a good place to start is learning about Sidereal time. It's going to be a foundational piece of information for what I plan to do. 

 

 

Astrophotography with Windows Phone Part 0 : Introduction

Last week I did a post on using a Windows Phone to control an RC car. I'm applying some of the information in that post to another project. You may have seen one of the astronomy applications that let's some one point their phone at the sky and identify a star or planet with it. I'm making the same type of application with the diference being that the one I'm making will also be controlling a telescope mount. The communication between the phone and the mount will occur over the same Bluetooth Tranceiver that I made iin the post about the car. The first iteration of this project will omit the Arduino. Telescope mounts from Celestron and Meade already contain microcontrollers so I'll start off by communicatin with what is built into the mount. At a later point I will need to reintroduce the Arduino to control other hardware (such as a camera). 

This project a targeting a special audiance; those interested in astronomy that already have a telescope with a computerized mount. Telescopes like this tend to be rather pricy. If you don't already have one I would not suggest picking up a telescope for the sake of experimenting with this project. Some of the less expensive telescops are available for around 500 USD. I'm using the CGEM 800 which at the time of this writing cost about 4 times more than that. For those that already have a computerized telescope if your telescope is another name brand (such as Meade) then you'll need to make adjustments to the code for the protocol that your telescope uses. The telescope that I'm using is on a right ascension mount. If yours is not you will also need to make adjustments to convert right ascension coordinates to altitude-azimuth coordinates. Once again I will be covering the math for this, but I don't have an alt-az telescope for testing. 

In the next post I'll introduce the hardware used to allow the Windows Phone and the Telescope to communicate with each other. A simple program will be introduced that will read and display telescope state; it won't control anything. In the next post after this I discuss the math of what needs to be done. The heavy lifting will all be explained here. After discussing all the math that is needed the third post will present the code in which the phone takes control of the telescope. 

There are also a few things that I won't be covering here. For this program I will only be tracking objects outside of our solar system. The sun and planets will be left out of this program. Unlike the non-solar stars the planets have this habit of wandering around the celestial sphere. They also influence each other's paths (especially Jupiter).  A discussion of the movement of the planets is worthy of it's own series of post. 

Next time: Astrophotography with Windows Phone Part 1: The Hardware Interface