In this tutorial we create a simple time-lapse capture script with Python for an INDI CCD camera.

First, download Python wrappers for INDI. You need subversion to get the source code and swig, python-dev and libindi-dev to compile it.

If you run Ubuntu or Debian just install the packages with:

sudo apt-get install subversion libindi-dev python-dev swig

If you use a Raspberry PI, just install INDI Library for Raspberry PI

Follow the instructions on the website to download, compile and install the warppers:

# download SVN repo
svn co svn://
# update FindINDI.cmake
wget -O swig-indi-python/cmake_modules/FindINDI.cmake
# change to build directory
mkdir libindipython
cd libindipython
# execute cmake with python2.7 path
cmake -D PYTHON_LIBRARY=/usr/lib/arm-linux-gnueabihf/ -D PYTHON_INCLUDE_DIR=/usr/include/python2.7/ ../swig-indi-python/
# build and install
sudo make install

Now we can test the wrappers. Open a second terminal and start an indi server with the CCD Simulator:

indiserver -v indi_simulator_ccd

Then switch to the first terminal and go to the downloaded swig-indi-python directory.

cd ../swig-indi-python/

There is the script Start it with:


The script will connect to your indiserver on localhost and then it looks for connected devices and their properties. The output for CCD Simulatoir should look like this:

2015-02-20 02:39:45,993 creating an instance of PyQtIndi.IndiClient
Connecting and waiting 2secs
Server connected (localhost:7624)
2015-02-20 02:39:45,993 new device CCD Simulator
2015-02-20 02:39:45,994 new property CONNECTION for device CCD Simulator
2015-02-20 02:39:45,994 new property DRIVER_INFO for device CCD Simulator
2015-02-20 02:39:45,994 new property DEBUG for device CCD Simulator
2015-02-20 02:39:45,994 new property CONFIG_PROCESS for device CCD Simulator
2015-02-20 02:39:45,994 new property SIMULATOR_SETTINGS for device CCD Simulator
2015-02-20 02:39:45,994 new property ON_TIME_FACTOR for device CCD Simulator
List of devices
CCD Simulator
List of Device Properties
-- CCD Simulator
       CONNECT(Connect)= Off
       DISCONNECT(Disconnect)= On
       DRIVER_NAME(Name)= CCD Simulator
       DRIVER_EXEC(Exec)= indi_simulator_ccd
       DRIVER_VERSION(Version)= 1.0
       DRIVER_INTERFACE(Interface)= 22
   > DEBUG
       ENABLE(Enable)= Off
       DISABLE(Disable)= On
       CONFIG_LOAD(Load)= Off
       CONFIG_SAVE(Save)= Off
       CONFIG_DEFAULT(Default)= Off
       SIM_XRES(CCD X resolution)= 1280.0
       SIM_YRES(CCD Y resolution)= 1024.0
       SIM_XSIZE(CCD X Pixel Size)= 5.2
       SIM_YSIZE(CCD Y Pixel Size)= 5.2
       SIM_MAXVAL(CCD Maximum ADU)= 65000.0
       SIM_BIAS(CCD Bias)= 10.0
       SIM_SATURATION(Saturation Mag)= 1.0
       SIM_LIMITINGMAG(Limiting Mag)= 17.0
       SIM_NOISE(CCD Noise)= 10.0
       SIM_SKYGLOW(Sky Glow (magnitudes))= 19.5
       SIM_OAGOFFSET(Oag Offset (arcminutes))= 0.0
       SIM_POLAR(PAE (arcminutes))= 0.0
       SIM_POLARDRIFT(PAE Drift (minutes))= 0.0
       1X(Actual Time)= On
       10X(10x)= Off
       100X(100x)= Off
INDI server localhost/7624 disconnected.
2015-02-20 02:39:46,995 Server disconnected (exit code = 0,localhost:7624)

But how to get images from the camera? It's easy! There is already a BaseClient in script:

import sys, time, logging
import PyIndi

class IndiClient(PyIndi.BaseClient):
    def __init__(self):
        super(IndiClient, self).__init__()
        self.logger = logging.getLogger('PyQtIndi.IndiClient')'creating an instance of PyQtIndi.IndiClient')
    def newDevice(self, d):"new device " + d.getDeviceName())"new device ")
    def newProperty(self, p):"new property "+ p.getName() + " for device "+ p.getDeviceName())"new property ")
    def removeProperty(self, p):"remove property "+ p.getName() + " for device "+ p.getDeviceName())
    def newBLOB(self, bp):"new BLOB "+
    def newSwitch(self, svp): ("new Switch "+ + " for device "+ svp.device.decode())
    def newNumber(self, nvp):"new Number "+ + " for device "+ nvp.device.decode())
    def newText(self, tvp):"new Text "+ + " for device "+ tvp.device.decode())
    def newLight(self, lvp):"new Light "+ + " for device "+ lvp.device.decode())
    def newMessage(self, d, m):"new Message "+ d.messageQueue(m).decode())
    def serverConnected(self):
        print("Server connected ("+self.getHost()+":"+str(self.getPort())+")")
    def serverDisconnected(self, code):"Server disconnected (exit code = "+str(code)+","+str(self.getHost())+":"+str(self.getPort())+")")

logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)

Just copy the code into a new script

The client inherits from INDI BaseClient and implements the virtual functions. These functions are called when the server sends information like new numbers (newNumber), new devices (newDevice) and so on. At the moment the new values are just printed to the terminal.

To test our client, we have to connect to the server. Just add the following code after your client:

# instantiate the client
# set indi server localhost and port 7624
# connect to indi server
print("Connecting and waiting 2secs")
if (not(indiclient.connectServer())):
     print("No indiserver running on "+indiclient.getHost()+":"+str(indiclient.getPort())+" - Try to run")
     print("  indiserver indi_simulator_telescope indi_simulator_ccd")

# start endless loop, client works asynchron in background
while True:

The code connects to the server and then it prevents the program from terminating with an endless loop (the client runs in background). If the connection was successfull, you will see some new properties. Now you can connect from another client for example Ekos to your INDI server. Connect Ekos to remote (localhost, 7624) and then connect to the CCD Simulator and change some settings (set exposure, gain etc.). You will see the server answers in Ekos and in your Python client.

To start an exposure with the Python client we have to connect our CCD camera and then set the "CCD_EXPOSURE" to a value > 0.

At first we just add a member variable "device" to our client:

class IndiClient(PyIndi.BaseClient):
    device = None

Then we edit the newDevice() function. This function is called when a new device is detected. We just test if the device is the CCD Simulator and then we save a reference in our member variable "device":

def newDevice(self, d):"new device " + d.getDeviceName())
    if d.getDeviceName() == "CCD Simulator":"Set new device CCD Simulator!")
        # save reference to the device in member variable
        self.device = d

Next step is to connect to CCD Simulator. We just wait for the CONNECTION property of CCD Simulator, connect to the device, set BLOB mode so we can get BLOBs (image data) and then we wait for the new property CCD_EXPOSURE by editing the newProperty() function:

def newProperty(self, p):"new property "+ p.getName() + " for device "+ p.getDeviceName())
    if self.device is not None and p.getName() == "CONNECTION" and p.getDeviceName() == self.device.getDeviceName():"Got property CONNECTION for CCD Simulator!")
        # connect to device
        # set BLOB mode to BLOB_ALSO
        self.setBLOBMode(1, self.device.getDeviceName(), None)
    if p.getName() == "CCD_EXPOSURE":
        # take first exposure

If CCD_EXPOSURE property was found, we call takeExposure(). in takeExposure() we just get the CCD_EXPOSURE property, set a new exposure time and send it back to the server/client:

def takeExposure(self):"<<<<<<<< Exposure >>>>>>>>>")
    #get current exposure time
    exp = self.device.getNumber("CCD_EXPOSURE")
    # set exposure time to 5 seconds
    exp[0].value = 5
    # send new exposure time to server/device

Now the CCD starts a new exposure and the newNumber() function is called every second. When the exposure finished, the newBlob() function with our image data is called. We just extract the image data from the BLOB and save it to a fits file ("") on our harddisk. Then we start a new exposure:

def newBLOB(self, bp):"new BLOB "+
    # get image data
    img = bp.getblobdata()
    import cStringIO
    # write image data to StringIO buffer
    blobfile = cStringIO.StringIO(img)
    # open a file and save buffer to disk
    with open("", "wb") as f:
    # start new exposure for timelapse images!

That's it!

At the moment we overwrite the fits file with every new image but you can change the name to current datetime or whatever to create a timelapse sequence.


3rd Party

Choose from the numerous 3rd party INDI drivers to suit your needs!

Got Problem?

Check out the FAQ, the forum, and the bug tracking system to resolve any issues you might have!
You can also subscribe to INDI newsletter and development mailing lists to get the latest updates on INDI!