diff --git a/.gitignore b/.gitignore index c6127b3..d12f46e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,18 +3,6 @@ # Object files *.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch # Libraries *.lib @@ -23,30 +11,11 @@ *.lo # Shared objects (inc. Windows DLLs) -*.dll *.so *.so.* -*.dylib -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf +# qt-creator +*.config +*.creator +*.files +*.includes diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..316d9dd --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +# run `make DEF=...` to add extra defines +PROGRAM := grasshopper +LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all +LDFLAGS += -lusefull_macros -lflycapture-c -lflycapture -L/usr/local/lib +SRCS := $(wildcard *.c) +DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 +OBJDIR := mk +CFLAGS += -O2 -Wno-trampolines -std=gnu99 +CFLAGS += -I/usr/local/include/flycapture +OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) +DEPS := $(OBJS:.o=.d) +CC = gcc +#CXX = g++ + + +all : $(OBJDIR) $(PROGRAM) + +debug: CFLAGS += -DEBUG -Werror -Wall -Wextra +debug: all + +$(PROGRAM) : $(OBJS) + @echo -e "\t\tLD $(PROGRAM)" + $(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM) + +$(OBJDIR): + mkdir $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +-include $(DEPS) +endif + +$(OBJDIR)/%.o: %.c + @echo -e "\t\tCC $<" + $(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $< + +clean: + @echo -e "\t\tCLEAN" + @rm -f $(OBJS) $(DEPS) + @rmdir $(OBJDIR) 2>/dev/null || true + +xclean: clean + @rm -f $(PROGRAM) + +.PHONY: clean xclean diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..ca26b63 --- /dev/null +++ b/Readme.md @@ -0,0 +1,4 @@ +Simplest tool for Grasshopper3 control +====================================== + +(pre-pre-alpha version) diff --git a/aux.c b/aux.c new file mode 100644 index 0000000..8e4cbeb --- /dev/null +++ b/aux.c @@ -0,0 +1,34 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include + +#include "aux.h" +#include "cmdlnopts.h" + +int verbose(verblevel levl, const char *fmt, ...){ + if((unsigned)verbose_level < levl) return 0; + va_list ar; int i; + va_start(ar, fmt); + i = vprintf(fmt, ar); + va_end(ar); + printf("\n"); + fflush(stdout); + return i; +} diff --git a/aux.h b/aux.h new file mode 100644 index 0000000..e61142d --- /dev/null +++ b/aux.h @@ -0,0 +1,34 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once +#ifndef AUX_H__ +#define AUX_H__ + +typedef enum{ + VERB_NONE, + VERB_MESG, + VERB_DEBUG +} verblevel; + +int verbose(verblevel levl, const char *fmt, ...); + +#define VMESG(...) do{verbose(VERB_MESG, __VA_ARGS__);}while(0) +#define VDBG(...) do{verbose(VERB_DEBUG, __VA_ARGS__);}while(0) + +#endif // AUX_H__ diff --git a/cmdlnopts.c b/cmdlnopts.c new file mode 100644 index 0000000..6d12e54 --- /dev/null +++ b/cmdlnopts.c @@ -0,0 +1,90 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include + +#include "cmdlnopts.h" + +static int help; + +/* + * here are global parameters initialisation + */ +int verbose_level; +glob_pars G; + +// default PID filename: +#define DEFAULT_PIDFILE "/tmp/grasshopper.pid" + +// DEFAULTS +// default global parameters +static glob_pars const Gdefault = { + .device = NULL, + .pidfile = DEFAULT_PIDFILE, + .exptime = NAN, +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +static myoption cmdlnopts[] = { +// common options + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, + {"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), _("camera device name")}, + {"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), _("pidfile (default: " DEFAULT_PIDFILE ")")}, + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&verbose_level), _("verbose level (each 'v' increases it)")}, + {"camno", NEED_ARG, NULL, 'n', arg_int, APTR(&G.camno), _("camera number (if many connected)")}, + {"exptime", NEED_ARG, NULL, 'x', arg_float, APTR(&G.exptime), _("exposure time (ms)")}, + end_option +}; + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + int i; + void *ptr; + ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); + size_t hlen = 1024; + char helpstring[1024], *hptr = helpstring; + snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); + // format of help: "Usage: progname [args]\n" + change_helpstring(helpstring); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + G.rest_pars_num = argc; + G.rest_pars = MALLOC(char *, argc); + for (i = 0; i < argc; i++) + G.rest_pars[i] = strdup(argv[i]); + } + return &G; +} + diff --git a/cmdlnopts.h b/cmdlnopts.h new file mode 100644 index 0000000..41ee0a7 --- /dev/null +++ b/cmdlnopts.h @@ -0,0 +1,41 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 . + */ + + +#pragma once +#ifndef CMDLNOPTS_H__ +#define CMDLNOPTS_H__ + +/* + * here are some typedef's for global data + */ +typedef struct{ + char *device; // camera device name + char *pidfile; // name of PID file + int rest_pars_num; // number of rest parameters + int camno; // number of camera to work with + float exptime; // exposition time + char** rest_pars; // the rest parameters: array of char* +} glob_pars; + + +glob_pars *parse_args(int argc, char **argv); +extern glob_pars G; +extern int verbose_level; + +#endif // CMDLNOPTS_H__ diff --git a/grasshopper.c b/grasshopper.c new file mode 100644 index 0000000..aa15e54 --- /dev/null +++ b/grasshopper.c @@ -0,0 +1,412 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "aux.h" +#include "cmdlnopts.h" + +static fc2Error err; +#define FC2FNE(fn, ...) do{if(FC2_ERROR_OK != (err=fn(__VA_ARGS__))){fc2DestroyContext(context); \ + ERRX(#fn "(): %s", fc2ErrorToDescription(err));}}while(0) + +#define FC2FNW(fn, ...) do{if(FC2_ERROR_OK != (err=fn(__VA_ARGS__))){fc2DestroyContext(context); \ + WARNX(#fn "(): %s", fc2ErrorToDescription(err)); return err;}}while(0) + + +void signals(int sig){ + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); + } + putlog("Exit with status %d", sig); + if(G.pidfile) // remove unnesessary PID file + unlink(G.pidfile); + restore_console(); + exit(sig); +} +#if 0 +typedef struct _Property +{ + /** Property info type. */ + fc2PropertyType type; + /** Flag indicating if the property is present. */ + BOOL present; + /** + * Flag controlling absolute mode (real world units) + * or non-absolute mode (camera internal units). + */ + BOOL absControl; + /** Flag controlling one push. */ + BOOL onePush; + /** Flag controlling on/off. */ + BOOL onOff; + /** Flag controlling auto. */ + BOOL autoManualMode; + /** + * Value A (integer). + * Used to configure properties in non-absolute mode. + */ + unsigned int valueA; + /** + * Value B (integer). For white balance, value B applies to the blue value and + * value A applies to the red value. + */ + unsigned int valueB; + /** + * Floating point value. + * Used to configure properties in absolute mode. + */ + float absValue; + /** Reserved for future use. */ + unsigned int reserved[8]; + + // For convenience, trigger delay is the same structure + // used in a separate function along with trigger mode. + +} fc2Property, fc2TriggerDelay; +typedef enum _fc2PropertyType +{ + FC2_BRIGHTNESS, + FC2_AUTO_EXPOSURE, + FC2_SHARPNESS, + FC2_WHITE_BALANCE, + FC2_HUE, + FC2_SATURATION, + FC2_GAMMA, + FC2_IRIS, + FC2_FOCUS, + FC2_ZOOM, + FC2_PAN, + FC2_TILT, + FC2_SHUTTER, + FC2_GAIN, + FC2_TRIGGER_MODE, + FC2_TRIGGER_DELAY, + FC2_FRAME_RATE, + FC2_TEMPERATURE, + FC2_UNSPECIFIED_PROPERTY_TYPE, + FC2_PROPERTY_TYPE_FORCE_32BITS = FULL_32BIT_VALUE + +} fc2PropertyType; +#endif + +static const char *propnames[] = { + [FC2_BRIGHTNESS] = "brightness", + [FC2_AUTO_EXPOSURE] = "auto exposure", + [FC2_SHARPNESS] = "sharpness", + [FC2_WHITE_BALANCE] = "white balance", + [FC2_HUE] = "hue", + [FC2_SATURATION] = "saturation", + [FC2_GAMMA] = "gamma", + [FC2_IRIS] = "iris", + [FC2_FOCUS] = "focus", + [FC2_ZOOM] = "zoom", + [FC2_PAN] = "pan", + [FC2_TILT] = "tilt", + [FC2_SHUTTER] = "shutter", + [FC2_GAIN] = "gain", + [FC2_TRIGGER_MODE] = "trigger mode", + [FC2_TRIGGER_DELAY] = "trigger delay", + [FC2_FRAME_RATE] = "frame rate", + [FC2_TEMPERATURE] = "temperature", + [FC2_UNSPECIFIED_PROPERTY_TYPE] = "unspecified" +}; + +static void prbl(char *s, BOOL prop){ + printf("\t%s = ", s); + if(prop) green("true"); + else red("false"); + printf("\n"); +} + +static fc2Error getproperty(fc2Context context, fc2PropertyType t){ + fc2Property prop; + prop.type = t; + FC2FNW(fc2GetProperty, context, &prop); + if(!prop.present) return FC2_ERROR_NOT_FOUND; + if(t <= FC2_UNSPECIFIED_PROPERTY_TYPE) green("\nProperty \"%s\":\n", propnames[t]); + prbl("absControl", prop.absControl); // 1 - world units, 0 - camera units + prbl("onePush", prop.onePush); // "one push" + prbl("onOff", prop.onOff); + prbl("autoManualMode", prop.autoManualMode); // 1 - auto, 0 - manual + printf("\tvalueA = %u\n", prop.valueA); // values in non-absolute mode + printf("\tvalueB = %u\n", prop.valueB); + printf("\tabsValue = %g\n", prop.absValue); // value in absolute mode + fc2PropertyInfo i; + i.type = t; + FC2FNW(fc2GetPropertyInfo, context, &i); + if(!i.present) return FC2_ERROR_OK; + green("Property Info:\n"); + prbl("autoSupported", i.autoSupported); // can be auto + prbl("manualSupported", i.manualSupported); // can be manual + prbl("onOffSupported", i.onOffSupported); // can be on/off + prbl("onePushSupported", i.onePushSupported); // can be "one push" + prbl("absValSupported", i.absValSupported); // can be absolute + prbl("readOutSupported", i.readOutSupported); // could be read out + printf("\tmin = %u\n", i.min); + printf("\tmax = %u\n", i.max); + printf("\tabsMin = %g\n", i.absMin); + printf("\tabsMax = %g\n", i.absMax); + printf("\tpUnits = %s\n", i.pUnits); + printf("\tpUnitAbbr = %s\n", i.pUnitAbbr); + return FC2_ERROR_OK; +} + +static fc2Error setexp(fc2Context context, float e){ + fc2Property prop; + prop.type = FC2_SHUTTER; + prop.autoManualMode = false; + prop.absValue = e; + FC2FNW(fc2SetProperty, context, &prop); + // now check + FC2FNW(fc2GetProperty, context, &prop); + if(fabs(prop.absValue - e) > 0.0001){ + WARNX("Can't set exposure! Got %g instead of %g.", prop.absValue, e); + return FC2_ERROR_FAILED; + } + return FC2_ERROR_OK; +} + + +static void PrintCameraInfo(fc2Context context, int n){ + fc2Error error; + fc2CameraInfo camInfo; + error = fc2GetCameraInfo(context, &camInfo); + if(error != FC2_ERROR_OK){ + WARNX("fc2GetCameraInfo(): %s", fc2ErrorToDescription(error)); + return; + } + printf("\n\n"); + green("*** CAMERA %d INFORMATION ***\n", n); + printf("Serial number - %u\n" + "Camera model - %s\n" + "Camera vendor - %s\n" + "Sensor - %s\n" + "Resolution - %s\n" + "Firmware version - %s\n" + "Firmware build time - %s\n\n", + camInfo.serialNumber, + camInfo.modelName, + camInfo.vendorName, + camInfo.sensorInfo, + camInfo.sensorResolution, + camInfo.firmwareVersion, + camInfo.firmwareBuildTime); + if(verbose_level >= VERB_DEBUG){ + for(fc2PropertyType t = FC2_BRIGHTNESS; t < FC2_UNSPECIFIED_PROPERTY_TYPE; ++t) + getproperty(context, t); + } +} + +static void SetTimeStamping(fc2Context context, BOOL enableTimeStamp) +{ + fc2Error error; + fc2EmbeddedImageInfo embeddedInfo; + + error = fc2GetEmbeddedImageInfo(context, &embeddedInfo); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2GetEmbeddedImageInfo: %s\n", fc2ErrorToDescription(error)); + } + + if (embeddedInfo.timestamp.available != 0) + { + embeddedInfo.timestamp.onOff = enableTimeStamp; + } + + error = fc2SetEmbeddedImageInfo(context, &embeddedInfo); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2SetEmbeddedImageInfo: %s\n", fc2ErrorToDescription(error)); + } +} + +static int GrabImages(fc2Context context, int numImagesToGrab) +{ + fc2Error error; + fc2Image rawImage; + fc2Image convertedImage; + fc2TimeStamp prevTimestamp = {0}; + int i; + + error = fc2CreateImage(&rawImage); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2CreateImage: %s\n", fc2ErrorToDescription(error)); + return -1; + } + + error = fc2CreateImage(&convertedImage); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2CreateImage: %s\n", fc2ErrorToDescription(error)); + return -1; + } + + // If externally allocated memory is to be used for the converted image, + // simply assigning the pData member of the fc2Image structure is + // insufficient. fc2SetImageData() should be called in order to populate + // the fc2Image structure correctly. This can be done at this point, + // assuming that the memory has already been allocated. + + for (i = 0; i < numImagesToGrab; i++) + { + // Retrieve the image + error = fc2RetrieveBuffer(context, &rawImage); + if (error != FC2_ERROR_OK) + { + printf("Error in retrieveBuffer: %s\n", fc2ErrorToDescription(error)); + return -1; + } + else + { + // Get and print out the time stamp + fc2TimeStamp ts = fc2GetImageTimeStamp(&rawImage); + int diff = (ts.cycleSeconds - prevTimestamp.cycleSeconds) * 8000 + + (ts.cycleCount - prevTimestamp.cycleCount); + prevTimestamp = ts; + printf("timestamp [%d %d] - %d\n", + ts.cycleSeconds, + ts.cycleCount, + diff); + } + } + + if (error == FC2_ERROR_OK) + { + // Convert the final image to RGB + error = fc2ConvertImageTo(FC2_PIXEL_FORMAT_MONO8, &rawImage, &convertedImage); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2ConvertImageTo: %s\n", fc2ErrorToDescription(error)); + return -1; + } + + // Save it to PNG + printf("Saving the last image to fc2TestImage.png \n"); + error = fc2SaveImage(&convertedImage, "fc2TestImage.png", FC2_PNG); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2SaveImage: %s\n", fc2ErrorToDescription(error)); + printf("Please check write permissions.\n"); + return -1; + } + } + + error = fc2DestroyImage(&rawImage); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2DestroyImage: %s\n", fc2ErrorToDescription(error)); + return -1; + } + + error = fc2DestroyImage(&convertedImage); + if (error != FC2_ERROR_OK) + { + printf("Error in fc2DestroyImage: %s\n", fc2ErrorToDescription(error)); + return -1; + } + + return 0; +} + +int main(int argc, char **argv){ + int ret = 0; + initial_setup(); + char *self = strdup(argv[0]); + parse_args(argc, argv); + check4running(self, G.pidfile); + FREE(self); + + signal(SIGTERM, signals); // kill (-15) - quit + signal(SIGHUP, SIG_IGN); // hup - ignore + signal(SIGINT, signals); // ctrl+C - quit + signal(SIGQUIT, signals); // ctrl+\ - quit + signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z + + setup_con(); + fc2Context context; + fc2PGRGuid guid; + unsigned int numCameras = 0; + + if(FC2_ERROR_OK != (err = fc2CreateContext(&context))){ + ERRX("fc2CreateContext(): %s", fc2ErrorToDescription(err)); + } + + FC2FNE(fc2GetNumOfCameras, context, &numCameras); + + if(numCameras == 0){ + fc2DestroyContext(context); + ERRX("No cameras detected!"); + } + + VMESG("Found %d camera[s]", numCameras); + if(verbose_level >= VERB_MESG){ + for(int i = 0; i < numCameras; ++i){ + FC2FNE(fc2GetCameraFromIndex, context, i, &guid); + FC2FNE(fc2Connect, context, &guid); + PrintCameraInfo(context, i); + } + } + FC2FNE(fc2GetCameraFromIndex, context, G.camno, &guid); + FC2FNE(fc2Connect, context, &guid); + if(verbose_level >= VERB_MESG && numCameras > 1) PrintCameraInfo(context, G.camno); + if(isnan(G.exptime)){ // no expose time -> return + goto destr; + } + if(FC2_ERROR_OK != setexp(context, G.exptime)){ + ret = 1; + goto destr; + } + VMESG("Set exposition to %gms", G.exptime); + + SetTimeStamping(context, TRUE); + + err = fc2StartCapture(context); + if (err != FC2_ERROR_OK) + { + fc2DestroyContext(context); + printf("Error in fc2StartCapture: %s\n", fc2ErrorToDescription(err)); + signals(12); + } + + if (GrabImages(context, 3) != 0) + { + fc2DestroyContext(context); + signals(12); + } + + err = fc2StopCapture(context); + if (err != FC2_ERROR_OK) + { + fc2DestroyContext(context); + printf("Error in fc2StopCapture: %s\n", fc2ErrorToDescription(err)); + signals(12); + } + +destr: + fc2DestroyContext(context); + signals(ret); + return ret; +}