vendredi 21 juillet 2017

Focuser automatique

Je trouve lassant de devoir sortir pour faire la mise au point du télescope, surtout au milieu de la nuit quand la température a beaucoup changé. J'ai donc décidé de construire un système pour le faire à distance et automatiquement.

C'est vrai que j'aurais pu acheter un système tout fait du type Robofocus. Mais ou est le fun là-dedans? Alors un Raspberry pi qui traine, un moteur pas-à-pas à 3€, un joint de plomberie, quelques bouts de bois, et le hardware est fait. C'est un prototype, hein, ne pas s'attendre a du tout beau.



Pour le software, il est en deux parties: le pi et le PC. Le Pi fait simplement tourner un serveur web (apache dans mon cas), qui sert une page PHP qui permet de contrôler à distance le moteur pas-a-pas à travers un appel a un script python.

<?php

if ($_GET["op"] == "forward") {
        if ($_GET["back"] != 1) {
                print("Morving forward ");
        }
        $steps = $_GET["steps"];
        if ($_GET["back"] != 1) {
                print($steps." steps\n");
        }
        exec("sudo python /home/cedric/motor.py forward ".$steps);
        if ($_GET["back"] == 1) {
                header("Location: index.php");
        }
} else if ($_GET["op"] == "backward") {
        if ($_GET["back"] != 1) {
                print("Morving backward ");
        }
        $steps = $_GET["steps"];
        if ($_GET["back"] != 1) {
                print($steps." steps\n");
        }
        exec("sudo python /home/cedric/motor.py backward ".$steps);
        if ($_GET["back"] == 1) {
                header("Location: index.php");
        }
} else {
        print("<form action=\"index.php\">\n");
        print("Direction: <select name=\"op\">\n<option value=\"forward\">In</option>\n<option value=\"backward\">Out</option>\n</select><br>\n");
        print("Steps: <input name=\"steps\"><br>\n");
        print("<input type=\"submit\"><br>\n");
        print("</form>\n");

        print("<a href=\"?op=forward&steps=100&back=1\">+100</a> <a href=\"?op=forward&steps=10&back=1\">+10</a> <a href=\"?op=forward&steps=1&back=1\">+1</a><br>\n");
        print("<a href=\"?op=backward&steps=100&back=1\">-100</a> <a href=\"?op=backward&steps=10&back=1\">-10</a> <a href=\"?op=backward&steps=1&back=1\">-1</a><br>\n");
}

?>

Le script python fabriqué avec des bouts de scripts trouvés en ligne pour le contrôle du moteur via GPIO. Il permet de commander avec les paramètres la direction du moteur et le nombre de pas.

#!/usr/bin/python

# Import required libraries
import sys
import time
import RPi.GPIO as GPIO

# Use BCM GPIO references
# instead of physical pin numbers
GPIO.setmode(GPIO.BCM)

# Define GPIO signals to use
# Physical pins 11,15,16,18
# GPIO17,GPIO22,GPIO23,GPIO24
StepPins = [17,22,23,24]

# Set all pins as output
for pin in StepPins:
  print "Setup pins"
  GPIO.setup(pin,GPIO.OUT)
  GPIO.output(pin, False)

# Define advanced sequence
# as shown in manufacturers datasheet
Seq = [[1,0,0,1],
       [1,0,0,0],
       [1,1,0,0],
       [0,1,0,0],
       [0,1,1,0],
       [0,0,1,0],
       [0,0,1,1],
       [0,0,0,1]]

StepCount = len(Seq)
StepDir = 1 # Set to 1 or 2 for clockwise
            # Set to -1 or -2 for anti-clockwise
WaitTime = 10/float(1000)
TotalSteps = 0

# Read wait time from command line
#if len(sys.argv)>1:
#  WaitTime = int(sys.argv[1])/float(1000)
#else:
#  WaitTime = 10/float(1000)
if len(sys.argv)>1:
  if sys.argv[1] == "forward":
    print "Going forward"
    StepDir = 1
  else:
    print "Going backward"
    StepDir = -1

if len(sys.argv)>2:
  TotalSteps = int(sys.argv[2])
  print "Moving " + str(TotalSteps) + " steps"

# Initialise variables
StepCounter = 0
StepsMade = int(0)

# Start main loop
while (StepsMade <= TotalSteps) or (TotalSteps == 0):
  print StepsMade
  print TotalSteps
  print StepCounter,
  print Seq[StepCounter]

  for pin in range(0, 4):
    xpin = StepPins[pin]
    if Seq[StepCounter][pin]!=0:
      print " Enable GPIO %i" %(xpin)
      GPIO.output(xpin, True)
    else:
      GPIO.output(xpin, False)

  StepCounter += StepDir
  StepsMade += 1

  # If we reach the end of the sequence
  # start again
  if (StepCounter>=StepCount):
    StepCounter = 0
  if (StepCounter<0):
    StepCounter = StepCount+StepDir

  # Wait before moving on
  time.sleep(WaitTime)

GPIO.cleanup();

Du coté du PC, je voulais intégrer autant que possible le focuser à mon système. C'est bien de pouvoir le contrôler par une page web, c'est mieux de le faire à travers les programmes habituels et encore mieux que ca soit automatique. Ca veut donc dire un driver ASCOM. Là j'ai galéré un peu parce que la documentation est un peu légère, et le 64bits/32bits cause beaucoup de problèmes avec ASCOM < 6.3 sur un ordinateur 64bits. Mais finalement, en dérivant d'exemples en ligne et en ajoutant quelques lignes pour faire des appels à la page web du pi, ca marche. Le code de la partie focuser:

using ASCOM.DeviceInterface;
using System;
using ASCOM;
using ASCOM.Utilities;
using System.Net.Http;
using System.IO;

class DeviceFocuser
{
    private Util util = new Util();
    private const int _StepSize = 1;
    private int focuserPosition = 0; // Class level variable to hold the current focuser position
    private const int focuserSteps = 10000;
    public string server;
    public bool trace = true;

    public DeviceFocuser()
    {

    }

    private void LogMessage(string message)
    {
        if (trace)
            File.AppendAllText(@"c:\temp\focuser.log", message+"\n");
    }

    public bool Connected // Dummy Connected method because this is referenced in the Link method
    {
        get { return false; }
        set { }
    }

    #region IFocuser Implementation

    public bool Absolute
    {
        get
        {
            LogMessage("Absolute Get "+ true.ToString());
            return false; // This is NOT an absolute focuser
        }
    }

    public void Halt()
    {
        LogMessage("Halt Not implemented");
        throw new ASCOM.MethodNotImplementedException("Halt");
    }

    public bool IsMoving
    {
        get
        {
            LogMessage("IsMoving Get "+ false.ToString());
            return false; // This focuser always moves instantaneously so no need for IsMoving ever to be True
        }
    }

    public bool Link
    {
        get
        {
            LogMessage("Link Get "+ this.Connected.ToString());
            return this.Connected; // Direct function to the connected method, the Link method is just here for backwards compatibility
        }
        set
        {
            LogMessage("Link Set "+ value.ToString());
            this.Connected = value; // Direct function to the connected method, the Link method is just here for backwards compatibility
        }
    }

    public int MaxIncrement
    {
        get
        {
            LogMessage("MaxIncrement Get "+ focuserSteps.ToString());
            return focuserSteps; // Maximum change in one move
        }
    }

    public int MaxStep
    {
        get
        {
            LogMessage("MaxStep Get "+ focuserSteps.ToString());
            return focuserSteps; // Maximum extent of the focuser, so position range is 0 to 10,000
        }
    }

    public void Move(int Position)
    {
        LogMessage("Move "+ Position.ToString());
        focuserPosition = Position; // Set the focuser position

        try
        {
            HttpClient _Client = new HttpClient();
            String request = server;
            if (Position > 0)
            {
                request += "?op=forward";
            }
            else
            {
                request += "?op=backward";
            }
            request += "&steps=" + Math.Abs(Position);
            LogMessage("URL is "+request);
            using (HttpResponseMessage response = _Client.GetAsync(request).Result)
            {
                using (HttpContent content = response.Content)
                {
                    // ... Read the string.
                    string result = content.ReadAsStringAsync().Result;
                    LogMessage("Move result " + result);
                }
            }
        }
        catch(Exception e)
        {
            LogMessage("Move error " + e.ToString());
            //throw;
        }
    }

    public int Position
    {
        get
        {
            return focuserPosition; // Return the focuser position
        }
    }

    public double StepSize
    {
        get
        {
            LogMessage("StepSize Get Not implemented");
            return _StepSize;
        }
    }

    public bool TempComp
    {
        get
        {
            LogMessage("TempComp Get "+ false.ToString());
            return false;
        }
        set
        {
            LogMessage("TempComp Set Not implemented");
            throw new ASCOM.PropertyNotImplementedException("TempComp", false);
        }
    }

    public bool TempCompAvailable
    {
        get
        {
            LogMessage("TempCompAvailable Get "+ false.ToString());
            return false; // Temperature compensation is not available in this driver
        }
    }

    public double Temperature
    {
        get
        {
            LogMessage("Temperature Get Not implemented");
            throw new ASCOM.PropertyNotImplementedException("Temperature", false);
        }
    }

    #endregion

    public void Dispose()
    {

    }
}

J'aurais pu honorer les requêtes de températures puisqu'un autre pi les offre à travers une API, mais pas vraiment la peine. Un programme de distribution et d'installation et hop, sur la machine qui contrôle le télescope.

Résultat: 2 demi-journées de travail et APT contrôle le focuser et peut gérer la mise au point automatique.

UPDATE: INDI driver, absolute positioning, persistent position. All on GitHub.

Aucun commentaire:

Enregistrer un commentaire