diff --git a/raptor_eagle_acqproc.cpp b/raptor_eagle_acqproc.cpp index 19575b3..b1de7fd 100644 --- a/raptor_eagle_acqproc.cpp +++ b/raptor_eagle_acqproc.cpp @@ -8,6 +8,37 @@ /*******************************************************************************/ +namespace details +{ + +double JulianDay(const std::chrono::utc_clock::time_point& tm) +{ + auto stp = std::chrono::time_point{std::chrono::utc_clock::to_sys(tm)}; + + const auto stp_days = std::chrono::floor(stp); + + const std::chrono::year_month_day ymd{stp_days}; + + int y = (int)ymd.year(); + unsigned m = (unsigned)ymd.month(); + unsigned D = (unsigned)ymd.day(); + + if (m < 3) { + m += 12; + y -= 1; + } + + // fraction of day + double df = std::chrono::duration>(stp - stp_days).count(); + + int C = static_cast(2.0 - y / 100.0 + y / 400.0); + int E = static_cast(365.25 * (y + 4716.0)), F = static_cast(30.6001 * (m + 1.0)); + + return C + D + E + F - 1524.5 + df; +} + +} // namespace details + RaptorEagleCCD::AcquisitionProcess::AcquisitionProcess(RaptorEagleCCD* manager) : _manager(manager) { std::stringstream st; @@ -26,6 +57,8 @@ RaptorEagleCCD::AcquisitionProcess::~AcquisitionProcess() void RaptorEagleCCD::AcquisitionProcess::start(const std::shared_ptr& params) { + isAcqInProgress = true; + // _acqParams = std::move(params); _acqParams = params; _acqParams->abortTime = std::chrono::utc_clock::time_point(); // to ensure the time point is in past @@ -74,15 +107,20 @@ void RaptorEagleCCD::AcquisitionProcess::start(const std::shared_ptrfilename.empty()) { + _manager->logWarn("An empty FITS filename is given! Do not save acquired image!"); + return; + } _manager->logInfo("Try to save FITS file with name '{}'", _acqParams->filename); fitsfile* fitsFilePtr; int status = 0; - int naxis = 2; - long naxes[naxis]; + // int naxis = 2; + long naxes[2]; naxes[0] = _acqParams->roiWidth; naxes[1] = _acqParams->roiHeight; @@ -90,33 +128,184 @@ void RaptorEagleCCD::AcquisitionProcess::start(const std::shared_ptrlogDebug("Create an empty FITS file ..."); fits_create_file(&fitsFilePtr, _acqParams->filename.c_str(), &status); - // keywords from user template file - if (_acqParams->templateFilename.size() && !status) { - _manager->logDebug("Copy keywords from '{}' template file", _acqParams->templateFilename); - fits_write_key_template(fitsFilePtr, _acqParams->templateFilename.c_str(), &status); - if (status) { // ignore possible errors - fits_get_errstatus(status, err_str); - _manager->logWarn( - "An error occured while copy keywords from the template file '{}' (err = {}, msg = {})! Ignore!", - _acqParams->templateFilename, status, err_str); + _manager->logDebug("Create primary FITS HDU (dim = [{}, {}])", naxes[0], naxes[1]); + fits_create_img(fitsFilePtr, USHORT_IMG, 2, naxes, &status); - status = 0; + _manager->logDebug("Write {} pixels to the HDU ...", npix); + fits_write_img(fitsFilePtr, USHORT_IMG, 1, (LONGLONG)npix, _imageBuffer.get(), &status); + + + // helper to convert std::string_view to C-lang null-terminated string + auto sv2cstr = [](const std::string_view& sv) { + static char buffer[72]; + size_t i = 0; + for (const char& el : sv) { + buffer[i++] = el; } - } + buffer[i] = '\0'; - // the keywords + return (const char*)buffer; + }; + + + + // the hardcoded camera setup-related keywords fits_update_key_str(fitsFilePtr, "ORIGIN", "SAO RAS", NULL, &status); fits_update_key_str(fitsFilePtr, "CREATOR", "RaptorEagleV control software", NULL, &status); + fits_update_key_str(fitsFilePtr, "FILE", _acqParams->filename.c_str(), "Original filename", &status); + fits_write_date(fitsFilePtr, &status); + + + std::string comm; + std::string str = std::format("{0:%F}T{0:%H}:{0:%M}:{0:%S}", _acqParams->startTime); + double jd = details::JulianDay(_acqParams->startTime); + + fits_update_key_str(fitsFilePtr, "DATE-OBS", str.c_str(), "Start of the exposure in UTC", &status); + fits_update_key_dbl(fitsFilePtr, "JD", jd, -12, "Julian day of exposure start time", &status); + fits_update_key_dbl(fitsFilePtr, "MJD", jd - 2400000.5, -12, "Modified Julian day of exposure start time", + &status); + + if (_acqParams->startTime < _acqParams->abortTime) { // acquisition was aborted + std::chrono::duration real_exp = _acqParams->abortTime - _acqParams->startTime; + _acqParams->expTime = real_exp.count(); + } + + fits_update_key_dbl(fitsFilePtr, "EXPTIME", _acqParams->expTime, -3, "Integration time in seconds", &status); + + + fits_update_key_lng(fitsFilePtr, "CRPIX1", 1, "Reference pixel along X-axis", &status); + fits_update_key_lng(fitsFilePtr, "CRVAL1", 1, "Physical value of the reference pixel", &status); + fits_update_key_lng(fitsFilePtr, "STARTX", _acqParams->roiStartX, + "Image area start pixel along X-axis [CCD pixel]", &status); + + + fits_update_key_lng(fitsFilePtr, "CRPIX2", 1, "Reference pixel along Y-axis", &status); + fits_update_key_lng(fitsFilePtr, "CRVAL2", 1, "Physical value of the reference pixel", &status); + fits_update_key_lng(fitsFilePtr, "STARTY", _acqParams->roiStartY, + "Image area start pixel along Y-axis [CCD pixel]", &status); + + fits_update_key_lng(fitsFilePtr, "XBIN", _acqParams->binX, "Horizontal binning", &status); + fits_update_key_lng(fitsFilePtr, "YBIN", _acqParams->binY, "Vertical binning", &status); + str = std::format("{}x{}", _acqParams->binX, _acqParams->binY); + fits_update_key_str(fitsFilePtr, "BINNING", str.c_str(), "Binning mode (XBINxYBIN)", &status); + + comm = "Shutter state: "; + comm += _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_CLOSED ? "always closed" + : _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_OPEN ? "always open" + : _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_EXP ? "open for exposure duration time" + : "unknown"; + fits_update_key_str(fitsFilePtr, "SHUTTER", sv2cstr(_acqParams->shutterState), comm.c_str(), &status); + + comm = "Camera readout rate "; + comm += _acqParams->readRate == CAMERA_ATTR_READ_RATE_FAST ? "(2 MHz)" + : _acqParams->readRate == CAMERA_ATTR_READ_RATE_SLOW ? "(75 kHz)" + : "unknown"; + fits_update_key_str(fitsFilePtr, "READRATE", sv2cstr(_acqParams->readRate), comm.c_str(), &status); + + fits_update_key_str(fitsFilePtr, "READMODE", sv2cstr(_acqParams->readMode), "Camera readout mode", &status); + + fits_update_key_str(fitsFilePtr, "GAIN", sv2cstr(_acqParams->gain), "Preamp gain", &status); + + str = _acqParams->tecState ? "ON" : "OFF"; + fits_update_key_str(fitsFilePtr, "TECSTATE", str.c_str(), "Thermoelectrical cooler state", &status); + + fits_update_key_dbl(fitsFilePtr, "CCDTEMP", _acqParams->ccdTemp, -2, "CCD chip temperature in Celsius", + &status); + fits_update_key_dbl(fitsFilePtr, "PCBTEMP", _acqParams->pcbTemp, -2, "PCB temperature in Celsius", &status); + fits_update_key_dbl(fitsFilePtr, "TECSETPT", _acqParams->tecSetPoint, -2, + "TEC set point temperature in Celsius", &status); + + if (!status) { + // keywords from user template file + if (_acqParams->templateFilename.size() && !status) { + _manager->logDebug("Copy keywords from '{}' template file", _acqParams->templateFilename); + fits_write_key_template(fitsFilePtr, _acqParams->templateFilename.c_str(), &status); + if (status) { // ignore possible errors + fits_get_errstatus(status, err_str); + _manager->logWarn( + "An error occured while copy keywords from the template file '{}' (err = {}, msg = {})! " + "Ignore!", + _acqParams->templateFilename, status, err_str); + + status = 0; + } + } + + // permanent keywords (may update keywords from template file!) + char card[80]; + char kname[8]; + int k_type; + + for (auto& s : _manager->_permanentFitsKeywords) { + fits_parse_template(s.data(), card, &k_type, &status); + if (status) { // ignore possible errors + fits_get_errstatus(status, err_str); + _manager->logWarn( + "An error occured while writing permanent keyword card [{}] (err = {}, msg = {})! " + "Ignore!", + s, status, err_str); + + status = 0; + } else { + for (int i = 0; i < 8; ++i) { + kname[i] = card[i]; + } + } + fits_update_card(fitsFilePtr, kname, card, &status); + } + + // keyword from user (may update template file and permanent keywords!) + for (auto& s : _manager->_currentFitsKeywords) { + fits_parse_template(s.data(), card, &k_type, &status); + if (status) { // ignore possible errors + fits_get_errstatus(status, err_str); + _manager->logWarn( + "An error occured while writing user keyword card [{}] (err = {}, msg = {})! " + "Ignore!", + s, status, err_str); + + status = 0; + } else { + for (int i = 0; i < 8; ++i) { + kname[i] = card[i]; + } + } + fits_update_card(fitsFilePtr, kname, card, &status); + } + } + + // hardcoded camera hardware version info keywords + + fits_update_key_ulng(fitsFilePtr, "SERNUM", (ULONGLONG)_manager->_cameraSerialNumber, "Camera serial number", + &status); + + str = std::format("{}.{}", _manager->_microVersion[0], _manager->_microVersion[1]); + fits_update_key_str(fitsFilePtr, "MICROVER", str.c_str(), "Camera microcontroller version", &status); + + str = std::format("{}.{}", _manager->_FPGAVersion[0], _manager->_FPGAVersion[1]); + fits_update_key_str(fitsFilePtr, "FPGAVER", str.c_str(), "Camera FPGA version", &status); + + str = std::format("{}", _manager->_buildDate); + fits_update_key_str(fitsFilePtr, "BUILDDAT", str.c_str(), "Camera build date, YY-MM-DD", &status); + + fits_update_key_str(fitsFilePtr, "BUILDCOD", sv2cstr(_manager->_buildCode), "Camera build code", &status); + fits_close_file(fitsFilePtr, &status); fits_get_errstatus(status, err_str); - _manager->logInfo("FITS file '{}' is saved", _acqParams->filename); + if (status) { + _manager->logError("An error occured while writing FITS file '{}'! FITS status = {} ({})", + _acqParams->filename, status, err_str); + } else { + _manager->logInfo("FITS file '{}' is saved", _acqParams->filename); + } }); } diff --git a/raptor_eagle_ccd.cpp b/raptor_eagle_ccd.cpp index 8eaa67a..07d50d2 100644 --- a/raptor_eagle_ccd.cpp +++ b/raptor_eagle_ccd.cpp @@ -782,8 +782,10 @@ void RaptorEagleCCD::startAquisition() .roiHeight = (*this)[CAMERA_ATTR_ROI_HEIGHT], .binX = (*this)[CAMERA_ATTR_XBIN], .binY = (*this)[CAMERA_ATTR_YBIN], + .shutterState = (*this)[CAMERA_ATTR_SHUTTER_STATE], .readRate = (*this)[CAMERA_ATTR_READ_RATE], .readMode = (*this)[CAMERA_ATTR_READ_MODE], + .gain = (*this)[CAMERA_ATTR_GAIN], .ccdTemp = (*this)[CAMERA_ATTR_CCD_TEMP], .tecSetPoint = (*this)[CAMERA_ATTR_TECPOINT], .tecState = (*this)[CAMERA_ATTR_TECSTATE] == CAMERA_ATTR_TECSTATE_ON ? true : false, @@ -866,6 +868,8 @@ void RaptorEagleCCD::initAttrComm() { logDebug("Try to create attributes and commands ..."); + auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; + // helper to setup 8-bit register attributes // 'validator' is a callable with signature: std::pair validator(const uchar&) auto create8BitAttr = [this](attr_ident_t name, auto reg_addr, auto&& validator, std::string_view log_mark) { @@ -1373,9 +1377,7 @@ void RaptorEagleCCD::initAttrComm() } }, adc::utils::AdcDefaultValueConverter<>::serialize, - [](const attribute_t::serialized_t& v) { - auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; - + [&comp_case_ignore](const attribute_t::serialized_t& v) { if (std::ranges::equal(v, CAMERA_ATTR_TECSTATE_ON, comp_case_ignore)) { return CAMERA_ATTR_TECSTATE_ON; } else if (std::ranges::equal(v, CAMERA_ATTR_TECSTATE_OFF, comp_case_ignore)) { @@ -1469,9 +1471,7 @@ void RaptorEagleCCD::initAttrComm() logDebug("Readout mode is set to 0x{:02X} bits", bits); }, adc::utils::AdcDefaultValueConverter<>::serialize, - [](const attribute_t::serialized_t& v) { - auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; - + [&comp_case_ignore](const attribute_t::serialized_t& v) { if (std::ranges::equal(v, CAMERA_ATTR_READ_MODE_NORMAL, comp_case_ignore)) { return CAMERA_ATTR_READ_MODE_NORMAL; } else if (std::ranges::equal(v, CAMERA_ATTR_READ_MODE_TEST, comp_case_ignore)) { @@ -1522,9 +1522,7 @@ void RaptorEagleCCD::initAttrComm() logDebug("Readout rate is set to [0x{:02X}, 0x{:02X}] bytes", bytes[0], bytes[1]); }, adc::utils::AdcDefaultValueConverter<>::serialize, - [](const attribute_t::serialized_t& v) { - auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; - + [&comp_case_ignore](const attribute_t::serialized_t& v) { if (std::ranges::equal(v, CAMERA_ATTR_READ_RATE_FAST, comp_case_ignore)) { return CAMERA_ATTR_READ_RATE_FAST; } else if (std::ranges::equal(v, CAMERA_ATTR_READ_RATE_SLOW, comp_case_ignore)) { @@ -1578,9 +1576,7 @@ void RaptorEagleCCD::initAttrComm() logDebug("Shutter state is set to 0x{:02X}", bytes[0]); }, adc::utils::AdcDefaultValueConverter<>::serialize, - [](const attribute_t::serialized_t& v) { - auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; - + [&comp_case_ignore](const attribute_t::serialized_t& v) { if (std::ranges::equal(v, CAMERA_ATTR_SHUTTER_STATE_EXP, comp_case_ignore)) { return CAMERA_ATTR_SHUTTER_STATE_EXP; } else if (std::ranges::equal(v, CAMERA_ATTR_SHUTTER_STATE_CLOSED, comp_case_ignore)) { @@ -1714,9 +1710,7 @@ void RaptorEagleCCD::initAttrComm() logDebug("Trigger mode bits are set to 0b{:08b}", bits); }, adc::utils::AdcDefaultValueConverter<>::serialize, - [](const attribute_t::serialized_t& v) { - auto comp_case_ignore = [](const auto& v1, const auto& v2) { return std::toupper(v1) == v2; }; - + [&comp_case_ignore](const attribute_t::serialized_t& v) { if (std::ranges::equal(v, CAMERA_ATTR_TRIGGER_MODE_EXT_RISING, comp_case_ignore)) { return CAMERA_ATTR_TRIGGER_MODE_EXT_RISING; } else if (std::ranges::equal(v, CAMERA_ATTR_TRIGGER_MODE_EXT_FALLING, comp_case_ignore)) { @@ -1732,5 +1726,38 @@ void RaptorEagleCCD::initAttrComm() return CAMERA_ATTR_STR_INVALID; }); + + addAttribute( + CAMERA_ATTR_GAIN, + [this]() { + auto bits = getFPGAState(); + + if (bits.test(CL_FPGA_CTRL_REG_HIGH_GAIN_BIT)) { + return CAMERA_ATTR_GAIN_HIGH; + } else { + return CAMERA_ATTR_GAIN_LOW; + } + }, + [this](const std::string_view& gain) { + if (gain == CAMERA_ATTR_GAIN_HIGH) { + setFPGAStateBit(CL_FPGA_CTRL_REG_HIGH_GAIN_BIT); + } else if (gain == CAMERA_ATTR_GAIN_LOW) { + clearFPGAStateBit(CL_FPGA_CTRL_REG_HIGH_GAIN_BIT); + } else { + logWarn("Invalid gain mode! Set it to {}", CL_FPGA_CTRL_REG_HIGH_GAIN_BIT); + setFPGAStateBit(CL_FPGA_CTRL_REG_HIGH_GAIN_BIT); + } + }, + adc::utils::AdcDefaultValueConverter<>::serialize, + [&comp_case_ignore](const attribute_t::serialized_t& v) { + if (std::ranges::equal(v, CAMERA_ATTR_GAIN_HIGH, comp_case_ignore)) { + return CAMERA_ATTR_GAIN_HIGH; + } else if (std::ranges::equal(v, CAMERA_ATTR_GAIN_LOW, comp_case_ignore)) { + return CAMERA_ATTR_GAIN_LOW; + } + + return CAMERA_ATTR_STR_INVALID; + }); + logDebug("Attributes and commands are successfully created!"); } diff --git a/raptor_eagle_ccd.h b/raptor_eagle_ccd.h index 2e42d1d..04c6d74 100644 --- a/raptor_eagle_ccd.h +++ b/raptor_eagle_ccd.h @@ -126,6 +126,9 @@ public: static constexpr std::string_view CAMERA_ATTR_SHUTTER_STATE_CLOSED{"CLOSED"}; // always closed static constexpr std::string_view CAMERA_ATTR_SHUTTER_STATE_EXP{"EXP"}; // open during acquisition + static constexpr std::string_view CAMERA_ATTR_GAIN_HIGH{"HIGH"}; + static constexpr std::string_view CAMERA_ATTR_GAIN_LOW{"LOW"}; + // external trigger, rising edge enabled static constexpr std::string_view CAMERA_ATTR_TRIGGER_MODE_EXT_RISING{"EXTERNAL_RISING"}; // external trigger, falling edge enabled @@ -162,8 +165,10 @@ private: uint8_t binX; uint8_t binY; + std::string_view shutterState; std::string_view readRate; std::string_view readMode; + std::string_view gain; double ccdTemp; double tecSetPoint;