lundi 12 mars 2018

Le télescope tweetteur

Quand le télescope travaille la nuit, je ne garde pas toujours un oeil dessus. Je regarde de temps en temps pour m'assurer que tout va bien, mais le reste du temps je laisse tourner. Quand le telescope suit un programme, j'aimerais bien savoir en temps réel ou il en est. J'ai donc créé un programme pour qu'il tweete.

Le programme utilise 2 librairies: le SDK de développement ASCOM (pour accéder à l'état du telescope) et Tweeninvi (une librairie de connection à twitter tres simple).

Le programme consiste d'une simple boucle qui demande l'état du télescope, résout sa position à partir des coordonnées de pointage (en utilisant SIMBAD), puis récupère une photo de la zone pointée (en utilisant SDSS).

Pour récupérer l'état du telescope, j'ai créé un wrapper pour ASCOM (just au cas ou je change la librairie). L'objet peut retourner les coordonnées et l'etat (tracking, parked, slewing) du telescope.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ASCOM.DriverAccess;
using ASCOM.Utilities;

namespace ObsState.Model
{
    public class ObsStateTelescope
    {
        private String TelescopeLocal = "EQMOD.Telescope";
        private String TelescopeEmulator = "EQMOD_SIM.Telescope";

        public String TelescopeId { get; private set; }
        private Telescope _telescope = null;

        public ObsStateTelescope(TelescopeType telescopeType)
        {
            if (telescopeType == TelescopeType.Local)
            {
                TelescopeId = TelescopeLocal;
            }
            else if (telescopeType == TelescopeType.Emulator)
            {
                TelescopeId = TelescopeEmulator;
            }
            else if (telescopeType == TelescopeType.Choose)
            {
                TelescopeId = Telescope.Choose("ASCOM.Simulator.Telescope");
            }
            else
            {
                throw new Exception("Unknown telescope type " + telescopeType);
            }
        }

        public void Connect()
        {
            if (String.IsNullOrEmpty(TelescopeId))
                throw new Exception("Telescope type not initialised properly");

            _telescope = new Telescope(TelescopeId)
            {
                Connected = true//, SiteElevation = 100, SiteLatitude = 45.892051, SiteLongitude = -0.223150, UTCDate = DateTime.UtcNow
            };
        }

        public bool Connected()
        {
            if (_telescope == null)
                throw new Exception("Telescope type not initialised properly");

            return _telescope.Connected;
        }

        public void Disconnect()
        {
            if (_telescope == null)
                throw new Exception("Telescope type not initialised properly");
            if (!_telescope.Connected)
                throw new Exception("Telescope not connected");

            _telescope.Connected = false;
            _telescope.Dispose();
        }

        public TelescopePosition Position()
        {
            if (_telescope == null)
                throw new Exception("Telescope type not initialised properly");
            if (!_telescope.Connected)
                throw new Exception("Telescope not connected");

            return new TelescopePosition()
                { SideralTime = _telescope.SiderealTime, Dec = _telescope.Declination, RA = _telescope.RightAscension };
        }

        public bool IsSlewing()
        {
            return _telescope.Slewing;
        }

        public bool IsTracking()
        {
            return _telescope.Tracking;
        }

        public bool IsParked()
        {
            return _telescope.AtPark;
        }
    }
}

La position du télescope est encapsulée dans un objet qui contient les données et quelques méthodes pratiques:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObsState
{
    public class TelescopePosition
    {
        public double SideralTime { get; internal set; }
        public double RA { get; internal set; }
        public double Dec { get; internal set; }

        public bool Equals(TelescopePosition tp)
        {
            bool equal = true;

            if (tp == null)
                return false;

            if ((Math.Abs(Math.Round(this.RA, 4)- Math.Round(tp.RA, 4))>0.01) || (Math.Abs(Math.Round(this.Dec, 4) - Math.Round(tp.Dec, 4)) > 0.01))
                equal = false;
            return equal;
        }

        public String RAToHMS()
        {
            String h = Math.Truncate(RA).ToString();
            double t = RA - Math.Truncate(RA);
            String m = Math.Truncate(t * 60).ToString();
            t = t * 60 - Math.Truncate(t * 60);
            String s = Math.Round(t * 60, 2).ToString();
            return h + "h" + m + "m" + s;
        }

        public String DecToDMS()
        {
            String d = Math.Truncate(Dec).ToString();
            double t = Dec - Math.Truncate(Dec);
            String m = Math.Truncate(t * 60).ToString();
            t = t * 60 - Math.Truncate(t * 60);
            String s = Math.Round(t * 60, 2).ToString();
            return d + "d" + m + "m" + s;
        }
    }
}

Une fois la position du telescope connue, la premiere étape est de trouver sur quoi il pointe pour pouvoir le tweeter. Ca se passe en envoyant une requête a SIMBAD:

Console.WriteLine("Loading SIMBAD data");
client = new HttpClient(); // Create HttpClient
buffer = client
.GetByteArrayAsync("http://simbad.u-strasbg.fr/simbad/sim-coo?Coord=" +
Math.Round(pos.RA, 10) +
"h" + Math.Round(pos.Dec, 10) + "d" +
"&CooFrame=FK5&CooEpoch=2000&CooEqui=2000&CooDefinedFrames=none&Radius=2&Radius.unit=arcmin&submit=submit%20query&CoordList=&output.format=ASCII")
.Result;
String data = Encoding.UTF8.GetString(buffer);
String target = new SIMBADResults(data).Parse();
if (String.IsNullOrEmpty(target))
target = "unknown";
Console.WriteLine("Target is " + target);

Les resultats sont analysees dans une petite classe. En gros on cherche quelque chose avec une magnitude (il est peu probable qu'on pointe vers quelque chose de super obscur qui n'a jamais ete analyse):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObsState
{
    public class SIMBADResults
    {
        private String _results;
        public SIMBADResults(String results)
        {
            _results = results;
        }

        public string Parse()
        {
            String temp = "";

            String[] lines = _results.Split('\n');
            int i = 0;
            while ((i<lines.Count()) && !lines[i].Contains("dist(asec)"))
            {
                i++;
            }
            if (i < lines.Count())
            {
                i++;
                i++;
                while ((i<lines.Count()) && (
                    lines[i].Split('|')[5].Trim().Equals(("~")) 
                    && lines[i].Split('|')[6].Trim().Equals(("~"))
                    && lines[i].Split('|')[7].Trim().Equals(("~"))
                    && lines[i].Split('|')[8].Trim().Equals(("~"))
                    && lines[i].Split('|')[9].Trim().Equals(("~"))
                    ))
                { 
                    i++;
                }
                if (i < lines.Count())
                {
                    temp = lines[i].Split('|')[2].Replace("*", "").Trim();
                }
            }
            return temp;
        }
    }
}

Ensuite, on cherche sur SDSS une image de la zone pointee histoire de donner une idee:

Console.WriteLine("Loading and parsing Skyview data");
client = new HttpClient(); // Create HttpClient
buffer = client
.GetByteArrayAsync(
"https://skyview.gsfc.nasa.gov/current/cgi/runquery.pl?Position=" +
pos.RAToHMS() + " " +
pos.DecToDMS() +
"&survey=DSS&coordinates=J2000&coordinates=&projection=Tan&pixels=300&size=5,5&float=on&scaling=Log&resolver=SIMBAD-NED&Sampler=_skip_&Deedger=_skip_&rotation=&Smooth=&lut=colortables%2Fb-w-linear.bin&PlotColor=&grid=_skip_&gridlabels=1&catalogurl=&CatalogIDs=on&survey=_skip_&survey=_skip_&survey=_skip_&IOSmooth=&contour=&contourSmooth=&ebins=null")
.Result;
data = Encoding.UTF8.GetString(buffer);
String imageURL = new SkyviewResults(data).Parse();
if (!String.IsNullOrEmpty(imageURL))
{
imageURL = "https://skyview.gsfc.nasa.gov/current/cgi/" + imageURL;
Console.WriteLine("Loading SDSS image");
client = new HttpClient(); // Create HttpClient
buffer = client.GetByteArrayAsync(imageURL).Result; // Download file

if (!String.IsNullOrEmpty(imageURL))
{
Console.WriteLine("Posting to Twitter");
twit.PublishMessage("Telescope is now pointing at " + target, buffer);
}
}
else
{
twit.PublishMessage("Telescope is now pointing at " + target);
}

Encore une fois les résultats sont analysés dans une petite classe:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObsState
{
    public class SkyviewResults
    {
        private String _results;

        public SkyviewResults(String results)
        {
            _results = results;
        }

        public string Parse()
        {
            String temp = "";

            String[] lines = _results.Split('\n');
            int i = 0;
            bool exit = false;
            while (i < lines.Count() && !exit)
            {
                if (lines[i].Length > 16 && lines[i].Substring(0, 16).Equals("x['_output_ql']="))
                {
                    exit = true;
                }
                else
                {
                    i++;
                }
            }
            if (i < lines.Count())
            {
                temp = lines[i].Split('=')[1].Replace("'", "").Trim();
            }
            return temp;
        }
    }
}

Le programme est démarré quand APT démarre et tourne dans une fenêtre minimisée.


Il y a encore des choses qui peuvent être améliorées, ce programme est le résultat d'une soirée de travail en démarrant sans même savoir comment poster sur twitter en c#. Mais c'est intéressant de le voir changer de cibles et travailler sur twitter pendant la nuit.

Note: il y a un probleme de linkage de DLL dans Tweetinvi qui fait que la DLL System.Net.Http.dll n'est pas copiée lors de la compilation. La solution est de l'ajouter a la main a la solution.


Aucun commentaire:

Enregistrer un commentaire