add Toupcam support; fix some bugs

This commit is contained in:
2026-02-11 17:20:35 +03:00
parent d10eeea99e
commit 28f33d9b62
11 changed files with 1330 additions and 216 deletions

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.20)
set(CCDLIB devtoupcam)
SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
find_package(PkgConfig REQUIRED)
pkg_check_modules(${CCDLIB} REQUIRED usefull_macros toupcam)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC)
add_library(${CCDLIB} SHARED ${SRC})
target_include_directories(${CCDLIB} PRIVATE ${${CCDLIB}_INCLUDE_DIRS} ..)
target_link_directories(${CCDLIB} PRIVATE ${${CCDLIB}_LIBRARY_DIRS})
target_link_libraries(${CCDLIB} ${${CCDLIB}_LIBRARIES} -fPIC)
install(TARGETS ${CCDLIB} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

639
TOUPCAM_cameras/toupcam.c Normal file
View File

@@ -0,0 +1,639 @@
/*
* This file is part of the CCD_Capture project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <float.h>
#include <string.h>
#include <toupcam.h>
#include <usefull_macros.h>
#include "ccdcapture.h"
extern cc_Camera camera;
extern cc_Focuser focuser;
extern cc_Wheel wheel;
// flags for image processing
typedef enum{
IM_SLEEP,
IM_STARTED,
IM_READY,
IM_ERROR
} imstate_t;
// devices
static ToupcamDeviceV2 g_dev[TOUPCAM_MAX] = {0};
static struct{
ToupcamDeviceV2* dev;
HToupcam hcam;
unsigned long long flags;
void* data;
imstate_t state;
} toupcam = {0};
static int camgetbp(uint8_t *bp);
// exptime and starting of exposition
static double exptime = 0., starttime = 0.;
#define TCHECK() do{if(!toupcam.hcam) return FALSE;}while(0)
/**
* @brief camcancel - camera.cancel - cancel exposition
*/
static void camcancel(){
if(!toupcam.hcam) return;
Toupcam_Trigger(toupcam.hcam, 0); // stop triggering
Toupcam_Stop(toupcam.hcam);
toupcam.state = IM_SLEEP;
}
// close camera device
static void camclose(){
camcancel();
if(toupcam.hcam){
DBG("Close camera");
Toupcam_Close(toupcam.hcam);
toupcam.hcam = NULL;
}
if(toupcam.data){
DBG("Free image data");
free(toupcam.data);
toupcam.data = NULL;
}
}
/**
* @brief initcam - init camera
* @return FALSE if failed
*/
static int initcam(){
camclose();
unsigned N = Toupcam_EnumV2(g_dev);
if(0 == N){
DBG("Found 0 toupcams");
return FALSE;
}
camera.Ndevices = (int) N;
return TRUE;
}
// callback of image ready event
static void EventCallback(unsigned nEvent, void _U_ *pCallbackCtx){
DBG("CALLBACK with evt %d", nEvent);
if(!toupcam.hcam || !toupcam.data){ DBG("NO data!"); return; }
if(nEvent != TOUPCAM_EVENT_IMAGE){ DBG("Not image event"); return; }
ToupcamFrameInfoV4 info = {0};
if(Toupcam_PullImageV4(toupcam.hcam, toupcam.data, 0, 0, 0, &info) < 0){
DBG("Error pulling image");
toupcam.state = IM_ERROR;
}else{
DBG("Image ready!");
toupcam.state = IM_READY;
camera.geometry.h = info.v3.height;
camera.geometry.w = info.v3.width;
}
}
/**
* @brief setdevno - camera.setDevNo - set active device number
* @param n - device no
* @return FALSE if no such device or failed
*/
static int setdevno(int n){
if(n < 0 || n >= camera.Ndevices) return FALSE;
camclose(); // close if was opened
toupcam.dev = &g_dev[n];
toupcam.hcam = Toupcam_Open(g_dev[n].id);
if(!toupcam.hcam){
DBG("Can't open %dth camera", n);
return FALSE;
}
DBG("Opened %s", toupcam.dev->displayname);
DBG("Clear ROI");
Toupcam_put_Roi(toupcam.hcam, 0, 0, 0, 0); // clear ROI
// now fill camera geometry
DBG("Get geometry");
Toupcam_get_Size(toupcam.hcam, &camera.geometry.w, &camera.geometry.h);
DBG("size (wxh): %dx%d", camera.geometry.w, camera.geometry.h);
unsigned int xoff, yoff, h, w;
DBG("Get ROI");
Toupcam_get_Roi(toupcam.hcam, &xoff, &yoff, &w, &h);
DBG("off (x/y): %d/%d; wxh: %dx%d", xoff, yoff, w, h);
camera.array.xoff = camera.field.xoff = xoff;
camera.array.yoff = camera.field.yoff = yoff;
camera.array.w = camera.field.w = w;
camera.array.h = camera.field.h = h;
DBG("Get pixel size");
Toupcam_get_PixelSize(toupcam.hcam, 0, &camera.pixX, &camera.pixY);
DBG("pixsize (x/y): %g/%g", camera.pixX, camera.pixY);
toupcam.flags = Toupcam_query_Model(toupcam.hcam)->flag;
DBG("flags: 0x%llx", toupcam.flags);
DBG("Allocate data (%d bytes)", 2 * camera.array.w * camera.array.h);
toupcam.data = calloc(camera.array.w * camera.array.h, 2);
#define OPT(opt, val, comment) do{DBG(comment); if(Toupcam_put_Option(toupcam.hcam, opt, val) < 0){ DBG("Can't put this option"); }}while(0)
OPT(TOUPCAM_OPTION_TRIGGER, 1, "Software/simulated trigger mode");
//OPT(TOUPCAM_OPTION_DFC, 0xff0000ff , "Enable dark field correction");
//OPT(TOUPCAM_OPTION_FFC, 0xff0000ff, "Enable flatfield correction");
OPT(TOUPCAM_OPTION_RAW, 1, "Put to RAW mode");
#undef OPT
toupcam.state = IM_SLEEP;
return TRUE;
}
/**
* @brief campoll - camera.pollcapture - polling of capture process status
* @param st (o) - status of capture process
* @param remain (o) - time remain (s)
* @return FALSE if error (exp aborted), TRUE while no errors
*/
static int campoll(cc_capture_status *st, float *remain){
TCHECK();
cc_capture_status curst;
double tremain = 0.f;
int ret = FALSE;
switch(toupcam.state){
case IM_SLEEP:
curst = CAPTURE_NO;
break;
case IM_ERROR:
curst = CAPTURE_ABORTED;
break;
case IM_READY:
curst = CAPTURE_READY;
ret = TRUE;
break;
default: // IM_PROCESS
curst = CAPTURE_PROCESS;
//DBG("exptime: %g, d-s: %g", exptime, sl_dtime() - starttime);
tremain = exptime - (sl_dtime() - starttime);
if(tremain < -2.0) curst = CAPTURE_ABORTED;
else{
if(tremain < 0.) tremain = 0.;
ret = TRUE;
}
}
//DBG("curst: %d, tremain: %g", curst, tremain);
if(st) *st = curst;
if(remain) *remain = (float)tremain;
return ret;
}
/**
* @brief startexp - camera.startexposition - start exp if can
* @return FALSE if failed
*/
static int startexp(){
TCHECK();
if(toupcam.state == IM_SLEEP){
if(Toupcam_StartPullModeWithCallback(toupcam.hcam, EventCallback, NULL) < 0){
WARNX("Can't run PullMode with Callback!");
return FALSE;
}
}
if(Toupcam_Trigger(toupcam.hcam, 1) < 0) return FALSE;
toupcam.state = IM_STARTED;
starttime = sl_dtime();
return TRUE;
}
/**
* @brief camcapt - camera.capture - capture an image, struct `ima` should be prepared before
* @param ima (o) - captured image
* @return FALSE if failed or bad `ima`
*/
static int camcapt(cc_IMG *ima){
TCHECK();
if(!ima || !ima->data || !toupcam.data) return FALSE;
uint8_t bp;
if(!camgetbp(&bp)) bp = 16;
size_t fullsz = camera.geometry.h * camera.geometry.w * (int)((bp+7)/8);
memcpy(ima->data, toupcam.data, fullsz);
ima->bitpix = bp;
ima->h = camera.geometry.h;
ima->w = camera.geometry.w;
ima->bytelen = fullsz;
return TRUE;
}
/**
* @brief camsetbit - camera.setbitdepth
* @param b - bit depth, 1 - high (16 bit), 0 - low (8 or other bit)
* @return FALSE if failed
*/
static int camsetbit(int b){
TCHECK();
DBG("set bitdepth %d", b);
if(Toupcam_put_Option(toupcam.hcam, TOUPCAM_OPTION_BITDEPTH, b) < 0) return FALSE;
int opt = (b) ? TOUPCAM_PIXELFORMAT_RAW16 : TOUPCAM_PIXELFORMAT_RAW8;
DBG("set pixel format %d", opt);
if(Toupcam_put_Option(toupcam.hcam, TOUPCAM_OPTION_PIXEL_FORMAT, opt) < 0) return FALSE;
return TRUE;
}
/**
* @brief camgetbp - camera.getbitpix - get bit depth in bits per pixel (8, 12, 16 etc)
* @param bp (o) - bits per pixel
* @return FALSE if failed
*/
static int camgetbp(uint8_t *bp){
TCHECK();
int b;
DBG("Get bitdepth");
if(Toupcam_get_Option(toupcam.hcam, TOUPCAM_OPTION_BITDEPTH, &b) < 0) return FALSE;
DBG("bitdepth=%d", b);
if(bp){
if(b == 0) *bp = 8;
else{
DBG("Get pixformat");
if(Toupcam_get_Option(toupcam.hcam, TOUPCAM_OPTION_PIXEL_FORMAT, &b) < 0) return FALSE;
DBG("pixformat=%d", b);
*bp = 8 + b*2;
}
}
return TRUE;
}
/**
* @brief camsetbrig - camera.setbrightness - set `brightness`
* @param b - `brightness` value
* @return FALSE if failed or no such property
*/
static int camsetbrig(float b){
TCHECK();
if(b < 0.f) return FALSE;
int br = (int) b;
DBG("Try to set brightness to %d", br);
if(Toupcam_put_Brightness(toupcam.hcam, br)) return FALSE;
DBG("OK");
return TRUE;
}
/**
* @brief camgetbrig - camera.getbrightness - get `brightness` value
* @param b (o) - brightness
* @return FALSE if failed or no such property
*/
static int camgetbrig(float *b){
TCHECK();
int br;
DBG("get brightness");
if(Toupcam_get_Brightness(toupcam.hcam, &br) < 0) return FALSE;
DBG("brightness=%d", br);
if(b) *b = (float) br;
return FALSE;
}
/**
* @brief camsetexp - camera.setexp - set exposition time (s)
* @param t - time (s)
* @return FALSE if failed
*/
static int camsetexp(float t){
TCHECK();
if(t < FLT_EPSILON) return FALSE;
unsigned int microseconds = (unsigned)(t * 1e6f);
DBG("Set exptime to %dus", microseconds);
if(Toupcam_put_ExpoTime(toupcam.hcam, microseconds) < 0) return FALSE;
DBG("OK");
exptime = (double) t;
return TRUE;
}
/**
* @brief camsetgain - camera.setgain - set gain
* @param g - gain
* @return FALSE if gain is wrong or no such property
*/
static int camsetgain(float g){
TCHECK();
unsigned short G = (unsigned short)(100.f * g);
if(Toupcam_put_ExpoAGain(toupcam.hcam, G) < 0){
unsigned short gmin, gmax, gdef;
if(Toupcam_get_ExpoAGainRange(toupcam.hcam, &gmin, &gmax, &gdef) >= 0)
WARNX("Gain out of range; min: %g, max: %g, default: %g",
(float)gmin/100.f, (float)gmax/100.f, (float)gdef/100.f);
return FALSE;
}
DBG("GAIN is %d", G);
return TRUE;
}
/**
* @brief camgetgain - camera.getgain - getter
* @param g (o) - gain
* @return FALSE if have no such property
*/
static int camgetgain(float *g){
TCHECK();
unsigned short G;
if(Toupcam_get_ExpoAGain(toupcam.hcam, &G) < 0) return FALSE;
if(g) *g = (float)G / 100.f;
return TRUE;
}
/**
* @brief camsett - camera.setT - set cold side temperature
* @param t - temperature (degC)
* @return FALSE if failed
*/
static int camsett(float t){
TCHECK();
if(!(toupcam.flags & TOUPCAM_FLAG_TEC)) return FALSE; // cannot set temperature
if(toupcam.flags & TOUPCAM_FLAG_TEC_ONOFF){
int onoff = (t < 20.f) ? 1 : 0;
if(Toupcam_put_Option(toupcam.hcam, TOUPCAM_OPTION_TEC, onoff) < 0) return FALSE;
if(!onoff) return TRUE; // just turn off TEC if user wants >= 20degC
}
short T = (short) t * 10.f;
DBG("Try to set T=%g", t);
if(Toupcam_put_Temperature(toupcam.hcam, T) < 0) return FALSE;
DBG("OK");
return TRUE;
}
/**
* @brief camgett - camera.getTcold - get cold side T
* @param t - temperature (degC)
* @return FALSE if failed or no such property
*/
static int camgettc(float *t){
TCHECK();
if(!(toupcam.flags & TOUPCAM_FLAG_GETTEMPERATURE)) return FALSE; // cannot get T
short T;
DBG("Try to get T");
if(Toupcam_get_Temperature(toupcam.hcam, &T) < 0) return FALSE;
DBG("got %u", T);
if(t) *t = ((float)T) / 10.f;
return TRUE;
}
/**
* @brief camgett - camera.getThot - get hot side T
* @param t - temperature (degC)
* @return FALSE if failed or no such property
*/
static int camgetth(float _U_ *t){
TCHECK();
return FALSE;
}
/**
* @brief camgett - camera.getTbody - get body T
* @param t - temperature (degC)
* @return FALSE if failed or no such property
*/
static int gettb(float _U_ *t){
TCHECK();
return FALSE;
}
/**
* @brief camsetbin - camera.setbin - binning setter
* @param h, v - binning values (horiz/vert)
* @return FALSE if failed or can't change binning
*/
static int camsetbin(int h, int v){
TCHECK();
if(h != v) return FALSE;
DBG("Try to set binning %d/%d", h,v);
if(Toupcam_put_Option(toupcam.hcam, TOUPCAM_OPTION_BINNING, h) < 0) return FALSE;
DBG("OK");
return TRUE;
}
/**
* @brief camgetbin - camera.getbin - binning getter
* @param binh (o), binv (o) - current binning
* @return FALSE if can't change binning
*/
static int camgetbin(int *binh, int *binv){
TCHECK();
int bin;
DBG("Get binning");
if(Toupcam_get_Option(toupcam.hcam, TOUPCAM_OPTION_BINNING, &bin) < 0) return FALSE;
DBG("Got: %d", bin);
if(binh) *binh = bin;
if(binv) *binv = bin;
return TRUE;
}
/**
* @brief camshutter - camera.shuttercmd - work with shutter
* @param s - new command for shutter (open/close etc)
* @return FALSE if failed or can't
*/
static int camshutter(cc_shutter_op _U_ s){
TCHECK();
if(!(toupcam.flags & TOUPCAM_FLAG_GLOBALSHUTTER)) return FALSE;
return FALSE;
}
/**
* @brief camsetgeom - camera.setgeometry - set geometry in UNBINNED coordinates
* @param f (i) - new geometry
* @return FALSE if can't change ROI or wrong geometry
*/
static int camsetgeom(cc_frameformat *f){
TCHECK();
if(Toupcam_put_Roi(toupcam.hcam, (unsigned) f->xoff, (unsigned) f->yoff, (unsigned) f->w, (unsigned) f->h) < 0) return FALSE;
camera.geometry = *f;
return TRUE;
}
/**
* @brief camgetnam - camera.getModelName - get model name
* @param n (io) - prepared string for name
* @param l - full length of n in bytes
* @return FALSE if can't
*/
static int camgetnam(char *n, int l){
TCHECK();
if(!toupcam.dev) return FALSE;
DBG("name: %s, strncpy to %d buf", toupcam.dev->displayname, l);
strncpy(n, toupcam.dev->displayname, l);
return FALSE;
}
/**
* @brief camgmg - camera.getmaxgain - get max available gain
* @param mg (o) - max gain
* @return FALSE if can't change gain
*/
static int camgmg(float _U_ *mg){
TCHECK();
return FALSE;
}
/**
* @brief camggl - camera.getgeomlimits - get limits of ROI changing
* @param max (o) - max ROI
* @param step (o) - step for ROI change
* @return
*/
static int camggl(cc_frameformat _U_ *max, cc_frameformat _U_ *step){
TCHECK();
return FALSE;
}
/**
* @brief camgetio - camera.getio - get IO status
* @param io (o) - GPIO status
* @return FALSE if have no such property
*/
static int camgetio(int _U_ *io){
TCHECK();
return FALSE;
}
/**
* @brief camfan - camera.setfanspeed - set fan speed
* @param spd - new speed
* @return FALSE if can't
*/
static int camfan(cc_fan_speed spd){
TCHECK();
if(!(toupcam.flags & TOUPCAM_FLAG_FAN)) return FALSE; // don't have a fan
DBG("Set fan to %d", spd);
if(Toupcam_put_Option(toupcam.hcam, TOUPCAM_OPTION_FAN, (int)spd) < 0){ DBG("Can't put this option"); }
return FALSE;
}
static cc_hresult setopt(const char *str, cc_charbuff *ans){
if(!str || !toupcam.hcam) return CC_RESULT_FAIL;
char key[256], *kptr = key;
snprintf(key, 255, "%s", str);
char *val = cc_get_keyval(&kptr);
if(!kptr || !val || strcmp(kptr, "opt")) return CC_RESULT_BADKEY;
snprintf(key, 255, "%s", val); // now this is our opt[=val]
kptr = key; val = cc_get_keyval(&kptr);
int result = -1, par;
int o;
if(!sl_str2i(&o, kptr)){
cc_charbufput(ans, "Wrong integer: ", 15);
cc_charbufaddline(ans, kptr);
}
DBG("optD: %u", o);
if(val){ // setter
par = atoi(val);
result = Toupcam_put_Option(toupcam.hcam, (unsigned) o, par);
}else{ // getter
result = Toupcam_get_Option(toupcam.hcam, (unsigned) o, &par);
if(result >= 0){
snprintf(key, 255, "Option %d have value %d", o, par);
cc_charbufaddline(ans, key);
}
}
if(result < 0 ) return CC_RESULT_FAIL;
return CC_RESULT_OK;
}
static cc_hresult getpf(const char *str, cc_charbuff *ans){
if(!str || !toupcam.hcam) return CC_RESULT_FAIL;
int N;
if(Toupcam_get_PixelFormatSupport(toupcam.hcam, -1, &N) < 0) return CC_RESULT_FAIL;
cc_charbufaddline(ans, "Supported formats:");
for(int f = 0; f < N; ++f){
int pf;
if(Toupcam_get_PixelFormatSupport(toupcam.hcam, f, &pf) < 0) continue;
cc_charbufaddline(ans, Toupcam_get_PixelFormatName(pf));
}
return CC_RESULT_SILENCE;
}
// cmd, help, checker, pointer, min, max, type
static cc_parhandler_t handlers[] = {
{"opt", "set/get given option, like opt=0x08=1 (TOUPCAM_OPTION_TEC ON) or opt=0x08 (check)", setopt, NULL, NULL, NULL, 0},
{"pixfmt", "get list of supported pixel formats", getpf, NULL, NULL, NULL, 0},
CC_PARHANDLER_END
};
/**
* @brief plugincmd - custom camera plugin command (get string as input, send string as output or NULL if failed)
* @param str
* @param buf
* @return
*/
static cc_hresult plugincmd(const char _U_ *str, cc_charbuff _U_ *buf){
return cc_plugin_customcmd(str, handlers, buf);
}
#if 0
// stub for nonexistant properties
static int stub(){
return FALSE;
}
// stub for void nonexistant functions
static void vstub(){
FNAME();
return;
}
#endif
// stub for nonexistant integer setters
static int istub(int _U_ N){
return FALSE;
}
/*
* Global objects: camera, focuser and wheel
*/
__attribute__ ((visibility("default"))) cc_Camera camera = {
.check = initcam,
.Ndevices = 0,
.close = camclose,
.pollcapture = campoll,
.capture = camcapt,
.cancel = camcancel,
.startexposition = startexp,
.plugincmd = plugincmd,
// setters:
.setDevNo = setdevno,
.setbrightness = camsetbrig,
.setexp = camsetexp,
.setgain = camsetgain,
.setT = camsett,
.setbin = camsetbin,
.setnflushes = istub,
.shuttercmd = camshutter,
.confio = istub,
.setio = istub,
.setframetype = istub,
.setbitdepth = camsetbit,
.setfastspeed = istub,
.setgeometry = camsetgeom,
.setfanspeed = camfan,
// getters:
.getbitpix = camgetbp,
.getbrightness = camgetbrig,
.getModelName = camgetnam,
.getgain = camgetgain,
.getmaxgain = camgmg,
.getgeomlimits = camggl,
.getTcold = camgettc,
.getThot = camgetth,
.getTbody = gettb,
.getbin = camgetbin,
.getio = camgetio,
// these parameters could be filled after initialization
.pixX = 10.,
.pixY = 10.,
.field = (cc_frameformat){.h = 1024, .w = 1024, .xoff = 10, .yoff = 10},
.array = (cc_frameformat){.h = 1050, .w = 1050, .xoff = 0, .yoff = 0},
.geometry = {0},
};