J2i.Net

Nothing at all and Everything in general.

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. 

 

 

Comments are closed