def set_frame(self, minx, miny, width, height):
        # passed in pixels are the "size" of the current binning
        #
        #
        # INDI expects us to set frame in 1x1 pixels
        #
        ccd_frame = indihelper.getNumber(self.cam, 'CCD_FRAME')
        if ccd_frame is None:
            return False
        binx, biny = self.get_binning()
        if None in [binx, biny]:
            logging.error('set_frame: unable to determine binning!')
            return False
        logging.debug(f'set_frame: binning = {binx} {biny}')
        ccd_x = indihelper.findNumber(ccd_frame, 'X')
        ccd_y = indihelper.findNumber(ccd_frame, 'Y')
        ccd_w = indihelper.findNumber(ccd_frame, 'WIDTH')
        ccd_h = indihelper.findNumber(ccd_frame, 'HEIGHT')
        # logging.debug(f'set_frame: {minx} {miny} {width} {height} {ccd_x.value} '
        #               f'{ccd_y.value} {ccd_w.value} {ccd_h.value}')

        if None in [ccd_x, ccd_y, ccd_w, ccd_h]:
            return False

        ccd_x.value = minx * binx
        ccd_y.value = miny * biny
        ccd_w.value = width * binx
        ccd_h.value = height * biny

        self.backend.indiclient.sendNewNumber(ccd_frame)
        return True
    def get_info(self):
        ccd_info = indihelper.getNumber(self.cam, 'CCD_INFO')

        if ccd_info is None:
            return None

        maxx = indihelper.findNumber(ccd_info, 'CCD_MAX_X')
        maxy = indihelper.findNumber(ccd_info, 'CCD_MAX_Y')
        pix_size = indihelper.findNumber(ccd_info, 'CCD_PIXEL_SIZE')
        pix_size_x = indihelper.findNumber(ccd_info, 'CCD_PIXEL_SIZE_X')
        pix_size_y = indihelper.findNumber(ccd_info, 'CCD_PIXEL_SIZE_Y')
        bpp = indihelper.findNumber(ccd_info, 'CCD_BITSPERPIXEL')

        # if maxx is None or maxy is None is pix_size is None \
        #   or pix_size_x is None or pix_size_y is None or bpp is None:
        if None in [maxx, maxy, pix_size, pix_size_x, pix_size_y, bpp]:
            return None

        obj = self.CCD_INFO()
        obj.CCD_MAX_X = maxx.value
        obj.CCD_MAX_Y = maxy.value
        obj.CCD_PIXEL_SIZE = pix_size.value
        obj.CCD_PIXEL_SIZE_X = pix_size_x.value
        obj.CCD_PIXEL_SIZE_Y = pix_size_y.value
        obj.CCD_BITSPERPIXEL = bpp.value

        return obj
    def get_frame(self):
        ccd_frame = indihelper.getNumber(self.cam, 'CCD_FRAME')
        if ccd_frame is None:
            return None

        ccd_x = indihelper.findNumber(ccd_frame, 'X')
        ccd_y = indihelper.findNumber(ccd_frame, 'Y')
        ccd_w = indihelper.findNumber(ccd_frame, 'WIDTH')
        ccd_h = indihelper.findNumber(ccd_frame, 'HEIGHT')

        if None in [ccd_x, ccd_y, ccd_w, ccd_h]:
            return (None, None, None, None)

        # called expects pixels are the "size" of the current binning
        #
        #
        # INDI returns frame in 1x1 pixels
        #
        binx, biny = self.get_binning()
        if None in [binx, biny]:
            logging.error('get_frame: unable to determine binning!')
            return False
        # logging.debug(f'get_frame: binning = {binx} {biny}')

        return (ccd_x.value // binx, ccd_y.value // biny,
                ccd_w.value // binx, ccd_h.value // biny)
 def get_binning(self):
     ccd_bin = indihelper.getNumber(self.cam, 'CCD_BINNING')
     binx = indihelper.findNumber(ccd_bin, 'HOR_BIN')
     biny = indihelper.findNumber(ccd_bin, 'VER_BIN')
     if binx is None or biny is None:
         return None, None
     return (binx.value, biny.value)
    def start_exposure(self, expos):
        logging.debug(f'Exposing image for {expos} seconds')

        # FIXME currently always requesting a light frame
        # FIXME need to check return codes of all steps
        if self.cam:
            ccd_exposure = indihelper.getNumber(self.cam, 'CCD_EXPOSURE')
            if ccd_exposure is None:
                return False

            # we should inform the indi server that we want to receive the
            # 'CCD1' blob from this device
            # FIXME not good to reference global 'config' here - we already pass
            #       a device object should combine?
            self.backend.indiclient.setBLOBMode(PyIndi.B_ALSO, self.name, 'CCD1')

            ccd_ccd1 = self.cam.getBLOB('CCD1')
            while not ccd_ccd1:
                time.sleep(0.25)
                ccd_ccd1 = self.device.getBLOB('CCD1')

            self.backend.indiclient.clearBlobEvent()

            ccd_expnum = indihelper.findNumber(ccd_exposure, 'CCD_EXPOSURE_VALUE')
            if ccd_expnum is None:
                return False
            ccd_expnum.value = expos
            self.backend.indiclient.sendNewNumber(ccd_exposure)

            return True
        else:
            return False
    def sync(self, ra, dec):
        """Sync to ra/dec with ra in decimal hours and dec in degrees"""
        logging.debug(f'sync to {ra} {dec}')
        #logging.debug('finding) ON_COORD_SET switch')
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'SYNC', True)
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'SLEW', False)
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'TRACK', False)

        #logging.debug('getNumber EQUATORIAL_EOD_COORD')
        eq_coord = indihelper.getNumber(self.mount, 'EQUATORIAL_EOD_COORD')
        if eq_coord is None:
            return False
        #logging.debug('findNumber "RA"')
        ra_coord = indihelper.findNumber(eq_coord, 'RA')
        if ra_coord is None:
            return False
        #logging.debug('findNumber "DEC"')
        dec_coord = indihelper.findNumber(eq_coord, 'DEC')
        if dec_coord is None:
            return False
        #logging.debug(f'{ra_coord.value} {dec_coord.value}')
        ra_coord.value = ra
        dec_coord.value = dec
        #logging.debug(f'{ra_coord.value} {dec_coord.value}')
        #logging.debug('sending Number')
        self.backend.indiclient.sendNewNumber(eq_coord)
        return True
 def set_binning(self, binx, biny):
     ccd_bin = indihelper.getNumber(self.cam, 'CCD_BINNING')
     if ccd_bin is None:
         return False
     num_binx = indihelper.findNumber(ccd_bin, 'HOR_BIN')
     num_biny = indihelper.findNumber(ccd_bin, 'VER_BIN')
     if num_binx is None or num_biny is None:
         return False
     num_binx.value = binx
     num_biny.value = biny
     self.backend.indiclient.sendNewNumber(ccd_bin)
     return True
 def get_cooler_power(self):
     if self.camera_has_cooler_power is False:
         return None
     cool_power = indihelper.getNumber(self.cam, 'CCD_COOLER_POWER')
     if cool_power is None:
         self.camera_has_cooler_power = False
         return None
     num = indihelper.findNumber(cool_power, 'CCD_COOLER_VALUE')
     if num is None:
         self.camera_has_cooler_power = False
         return None
     return num.value
    def get_min_max_exposure(self):
        if self.cam:
            ccd_exposure = indihelper.getNumber(self.cam, 'CCD_EXPOSURE')
            if ccd_exposure is None:
                return None

            ccd_expnum = indihelper.findNumber(ccd_exposure, 'CCD_EXPOSURE_VALUE')
            if ccd_expnum is None:
                return None

            return (ccd_expnum.min, ccd_expnum.max)
        else:
            return None
    def get_camera_usbbandwidth(self):
        """ Looks for camera specific usb traffic - only works for ASI afaik"""
        if self.cam:
            ccd_controls = indihelper.getNumber(self.cam, 'CCD_CONTROLS')
            if ccd_controls is None:
                return None

            ccd_usb = indihelper.findNumber(ccd_controls, 'BandWidth')
            if ccd_usb is None:
                return None

            return ccd_usb.value
        else:
            return None
    def get_camera_offset(self):
        """ Looks for camera specific offset - only works for ASI afaik"""
        if self.cam:
            ccd_controls = indihelper.getNumber(self.cam, 'CCD_CONTROLS')
            if ccd_controls is None:
                return None

            ccd_offset = indihelper.findNumber(ccd_controls, 'Offset')
            if ccd_offset is None:
                return None

            return ccd_offset.value
        else:
            return None
    def get_camera_gain(self):
        """ Looks for camera specific gain - only works for ASI afaik"""
        if self.cam:
            ccd_controls = indihelper.getNumber(self.cam, 'CCD_CONTROLS')
            if ccd_controls is None:
                return None

            ccd_gain = indihelper.findNumber(ccd_controls, 'Gain')
            if ccd_gain is None:
                return None

            return ccd_gain.value
        else:
            return None
    def get_max_binning(self):
        if self.cam:
            ccd_bin = indihelper.getNumber(self.cam, 'CCD_BINNING')
            #logging.info(f'ccd_bin={ccd_bin}')
            if ccd_bin is None:
                return None

            ccd_hbin = indihelper.findNumber(ccd_bin, 'HOR_BIN')
            #logging.info(f'ccd_hbin={indihelper.dump_Number(ccd_hbin)}')
            if ccd_hbin is None:
                return None

            return ccd_hbin.max
        else:
            return None
    def slew(self, ra, dec):
        """Slew to ra/dec with ra in decimal hours and dec in degrees"""
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'SLEW', False)
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'TRACK',  True)
        indihelper.setfindSwitchState(self.backend.indiclient, self.mount,
                                      'ON_COORD_SET', 'SYNC', False)

        eq_coord = indihelper.getNumber(self.mount, 'EQUATORIAL_EOD_COORD')
        if eq_coord is None:
            return False

        self.slew_eqcoord = eq_coord
        ra_coord = indihelper.findNumber(eq_coord, 'RA')
        if ra_coord is None:
            return False
        dec_coord = indihelper.findNumber(eq_coord, 'DEC')
        if dec_coord is None:
            return False
        ra_coord.value = ra
        dec_coord.value = dec
        self.backend.indiclient.sendNewNumber(eq_coord)
        return True