diff --git a/camerad/archon_controller.cpp b/camerad/archon_controller.cpp index 02117f0..b50d761 100644 --- a/camerad/archon_controller.cpp +++ b/camerad/archon_controller.cpp @@ -7,6 +7,8 @@ * */ +#include + #include "archon_controller.h" #include "archon_interface.h" #include @@ -571,7 +573,7 @@ namespace Camera { while (reply_ptr < reply_end && *reply_ptr != '=' && *reply_ptr != ' ') reply_ptr++; if (reply_ptr >= reply_end || *reply_ptr != '=') break; - size_t key_len = reply_ptr - key_start; + const std::string_view key(key_start, reply_ptr - key_start); reply_ptr++; // skip "=" // find value @@ -580,62 +582,43 @@ namespace Camera { size_t valuelen = reply_ptr - value_start; // TIMER=XXXX pattern - if (key_len==5 && key_start[0]=='T' && std::strncmp(key_start, "TIMER", 5)==0) { + if (key == "TIMER") { this->frameinfo.timer.assign(value_start, valuelen); } else - if (key_len==4) { - // RBUF=XXXX pattern - if (key_start[0]=='R' && std::strncmp(key_start, "RBUF", 4)==0) { - this->frameinfo.rbuf = std::atoi(value_start); - } - else - // WBUF=XXXX pattern - if (key_start[0]=='W' && std::strncmp(key_start, "WBUF", 4)==0) { - this->frameinfo.wbuf = std::atoi(value_start); - } + // RBUF=XXXX pattern + if (key == "RBUF") { + this->frameinfo.rbuf = std::atoi(value_start); + } + else + // WBUF=XXXX pattern + if (key == "WBUF") { + this->frameinfo.wbuf = std::atoi(value_start); } else // BUFnXXXX=XXXX pattern... - if (key_len>3 && key_start[0]=='B' && key_start[1]=='U' && key_start[2]=='F') { - int bufnum = key_start[3]-'1'; // convert to 0-based - - // match suffix - const char* suffix = key_start+4; - size_t suffix_len = key_len-4; - - switch (suffix_len) { - case 4: // BUFnBASE, MODE - if (std::strncmp(suffix, "BASE", 4)==0) this->frameinfo.bufbase[bufnum] = std::strtoul(value_start, nullptr, 10); - else - if (std::strncmp(suffix, "MODE", 4)==0) this->frameinfo.bufmode[bufnum] = std::atoi(value_start); - break; - case 5: // BUFnFRAME, WIDTH, LINES - if (std::strncmp(suffix, "FRAME", 5)==0) this->frameinfo.bufframen[bufnum] = std::atoi(value_start); - else - if (std::strncmp(suffix, "WIDTH", 5)==0) this->frameinfo.bufwidth[bufnum] = std::atoi(value_start); - else - if (std::strncmp(suffix, "LINES", 5)==0) this->frameinfo.buflines[bufnum] = std::atoi(value_start); - break; - case 6: // BUFnSAMPLE, PIXELS, HEIGHT - if (std::strncmp(suffix, "SAMPLE", 6)==0) this->frameinfo.bufsample[bufnum] = std::atoi(value_start); - else - if (std::strncmp(suffix, "PIXELS", 6)==0) this->frameinfo.bufpixels[bufnum] = std::atoi(value_start); - else - if (std::strncmp(suffix, "HEIGHT", 6)==0) this->frameinfo.bufheight[bufnum] = std::atoi(value_start); - break; - case 8: // BUFnCOMPLETE - if (std::strncmp(suffix, "COMPLETE", 8)==0) this->frameinfo.bufcomplete[bufnum] = std::atoi(value_start); - break; - case 9: // BUFnTIMESTAMP - if (std::strncmp(suffix, "TIMESTAMP", 9)==0) this->frameinfo.buftimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); - break; - case 11: // BUFnRETIMESTAMP, FETIMESTAMP - if (std::strncmp(suffix, "RETIMESTAMP", 11)==0) this->frameinfo.bufretimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); - else - if (std::strncmp(suffix, "FETIMESTAMP", 11)==0) this->frameinfo.buffetimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); - break; - } // end switch(suffix_len) + if (key.size()>3 && key[0]=='B' && key[1]=='U' && key[2]=='F') { + int bufnum = key[3]-'1'; // convert to 0-based + + // match suffix; string_view compares length before bytes, so this + // stays as cheap as the switch-on-length it replaces + const std::string_view suffix = key.substr(4); + + if (suffix == "BASE") this->frameinfo.bufbase[bufnum] = std::strtoul(value_start, nullptr, 10); + else if (suffix == "MODE") this->frameinfo.bufmode[bufnum] = std::atoi(value_start); + else if (suffix == "FRAME") this->frameinfo.bufframen[bufnum] = std::atoi(value_start); + else if (suffix == "WIDTH") this->frameinfo.bufwidth[bufnum] = std::atoi(value_start); + else if (suffix == "LINES") this->frameinfo.buflines[bufnum] = std::atoi(value_start); + else if (suffix == "SAMPLE") this->frameinfo.bufsample[bufnum] = std::atoi(value_start); + else if (suffix == "PIXELS") this->frameinfo.bufpixels[bufnum] = std::atoi(value_start); + else if (suffix == "HEIGHT") this->frameinfo.bufheight[bufnum] = std::atoi(value_start); + else if (suffix == "COMPLETE") this->frameinfo.bufcomplete[bufnum] = std::atoi(value_start); + else if (suffix == "RAWLINES") this->frameinfo.bufrawlines[bufnum] = std::atoi(value_start); + else if (suffix == "TIMESTAMP") this->frameinfo.buftimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); + else if (suffix == "RAWOFFSET") this->frameinfo.bufrawoffset[bufnum] = std::strtoul(value_start, nullptr, 10); + else if (suffix == "RAWBLOCKS") this->frameinfo.bufrawblocks[bufnum] = std::atoi(value_start); + else if (suffix == "RETIMESTAMP") this->frameinfo.bufretimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); + else if (suffix == "FETIMESTAMP") this->frameinfo.buffetimestamp[bufnum] = std::strtoull(value_start, nullptr, 16); } // end if BUFnXXXX pattern } // end looping through reply @@ -1794,8 +1777,10 @@ namespace Camera { this->get_configmap_value("FRAMEMODE", mode->geometry.framemode); this->get_configmap_value("RAWENABLE", mode->rawenable); this->get_configmap_value("RAWSEL", this->rawinfo.adchan); - this->get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); - this->get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); + this->get_configmap_value("RAWSAMPLES", this->rawinfo.samples); + this->get_configmap_value("RAWSTARTLINE", this->rawinfo.startline); + this->get_configmap_value("RAWENDLINE", this->rawinfo.endline); + this->get_configmap_value("RAWSTARTPIXEL", this->rawinfo.startpixel); // Read geometry from the mode's configmap (not the global one) since each // mode section can override LINECOUNT/PIXELCOUNT @@ -2173,11 +2158,10 @@ namespace Camera { // switch (this->frametype) { case Camera::ArchonController::FRAME_RAW: - // Archon buffer base address + // RAW data lives at bufbase + rawoffset; block count derives from the + // RAW geometry (RAWSAMPLES x rawlines x 2 bytes), never image_memory bufaddr = this->frameinfo.bufbase[index] + this->frameinfo.bufrawoffset[index]; - - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = (unsigned int) floor( (this->interface->camera_info.image_memory + BLOCK_LEN - 1 ) / BLOCK_LEN ); + bufblocks = (this->raw_frame_bytes() + BLOCK_LEN - 1) / BLOCK_LEN; break; case Camera::ArchonController::FRAME_IMAGE: { @@ -2316,6 +2300,199 @@ namespace Camera { } + /***** Camera::ArchonController::is_raw_config_key *************************/ + /** + * @brief return true if key is one of the settable RAW config keywords + */ + bool ArchonController::is_raw_config_key(const std::string &key) { + for (const auto* raw_key : {"RAWENABLE", "RAWSEL", "RAWSTARTLINE", + "RAWENDLINE", "RAWSTARTPIXEL", "RAWSAMPLES"}) { + if (key == raw_key) return true; + } + return false; + } + /***** Camera::ArchonController::is_raw_config_key *************************/ + + + /***** Camera::ArchonController::raw_geometry ****************************/ + /** + * @brief resolve RAW capture geometry for the newest buffer + * @details Prefers the controller-reported BUFnRAWBLOCKS/BUFnRAWLINES, + * which already account for the per-line rounding to whole + * 1024-byte blocks. Falls back to the config keys when the + * controller has not reported them (e.g. emulator). The line + * range is inclusive: RAWSTARTLINE=0,RAWENDLINE=1 is two lines. + */ + ArchonController::raw_geometry_t ArchonController::raw_geometry() const { + const auto index = this->frameinfo.index.load(); + raw_geometry_t geom; + geom.samples = static_cast(this->rawinfo.samples); + geom.blocks_per_line = static_cast(this->frameinfo.bufrawblocks[index]); + geom.lines = static_cast(this->frameinfo.bufrawlines[index]); + + if (geom.blocks_per_line == 0 || geom.lines == 0) { + geom.blocks_per_line = + (static_cast(geom.samples) * sizeof(uint16_t) + BLOCK_LEN - 1) / BLOCK_LEN; + const int span = this->rawinfo.endline - this->rawinfo.startline + 1; + geom.lines = span > 0 ? static_cast(span) : 0u; + } + return geom; + } + /***** Camera::ArchonController::raw_geometry ****************************/ + + + /***** Camera::ArchonController::raw_frame_bytes **************************/ + /** + * @brief padded, size-aware byte count for one RAW fetch + * @details Each line occupies whole 1024-byte blocks, so the fetch reads + * blocks_per_line x lines blocks. RAW samples are always 16-bit; + * RAWSTARTPIXEL only sets where sampling begins in a line and so + * does not change the count. + */ + uint32_t ArchonController::raw_frame_bytes() const { + const raw_geometry_t geom = this->raw_geometry(); + return geom.blocks_per_line * geom.lines * BLOCK_LEN; + } + /***** Camera::ArchonController::raw_frame_bytes **************************/ + + + /***** Camera::ArchonController::get_raw_config **************************/ + /** + * @brief report the current RAW configuration keywords + * @param[out] retstring space-delimited KEY=VALUE pairs + */ + long ArchonController::get_raw_config(std::string &retstring) { + std::ostringstream oss; + for (const auto* key : {"RAWENABLE", "RAWSEL", "RAWSTARTLINE", + "RAWENDLINE", "RAWSTARTPIXEL", "RAWSAMPLES"}) { + auto it = this->configmap.find(key); + oss << key << "=" << (it != this->configmap.end() ? it->second.value : "?") << " "; + } + retstring = oss.str(); + return NO_ERROR; + } + /***** Camera::ArchonController::get_raw_config **************************/ + + + /***** Camera::ArchonController::set_raw_config **************************/ + /** + * @brief set one or more RAW config keywords then APPLYCDS + * @param[in] args "KEY VALUE [KEY VALUE ...]" + * @param[out] retstring resulting RAW configuration + */ + long ArchonController::set_raw_config(const std::string &args, std::string &retstring) { + const std::string function("Camera::ArchonController::set_raw_config"); + + std::vector tokens; + Tokenize(args, tokens, " "); + if (tokens.empty() || tokens.size() % 2 != 0) { + logwrite(function, "ERROR expected KEY VALUE pairs"); + retstring = "expected KEY VALUE pairs"; + return ERROR; + } + + bool changed = false; + for (size_t i = 0; i < tokens.size(); i += 2) { + std::string key = tokens[i]; + std::transform(key.begin(), key.end(), key.begin(), ::toupper); + if (!is_raw_config_key(key)) { + logwrite(function, "ERROR unknown RAW key: "+key); + retstring = "unknown RAW key: "+key; + return ERROR; + } + if (this->write_config_key(key.c_str(), tokens[i+1].c_str(), changed) != NO_ERROR) { + retstring = "failed writing "+key; + return ERROR; + } + } + + // Archon has no way to report whether a pending config write has already + // been applied, so always APPLYCDS instead of trying to infer if it's needed + if (this->send_cmd(APPLYCDS) != NO_ERROR) { + logwrite(function, "ERROR applying RAW configuration"); + retstring = "failed to apply"; + return ERROR; + } + + // refresh cached geometry from the now-updated configmap + this->get_configmap_value("RAWSEL", this->rawinfo.adchan); + this->get_configmap_value("RAWSAMPLES", this->rawinfo.samples); + this->get_configmap_value("RAWSTARTLINE", this->rawinfo.startline); + this->get_configmap_value("RAWENDLINE", this->rawinfo.endline); + this->get_configmap_value("RAWSTARTPIXEL", this->rawinfo.startpixel); + + return this->get_raw_config(retstring); + } + /***** Camera::ArchonController::set_raw_config **************************/ + + + /***** Camera::ArchonController::read_raw *******************************/ + /** + * @brief retrieve RAW (pre-CDS) data from the newest frame buffer + * @details Reads the size-aware RAW region as 16-bit unsigned samples and + * dispatches it in-band as a (RAWSAMPLES x rawlines) frame, + * independent of the post-CDS pixel mode. + * @param[out] retstring "samples= lines= bytes=" + */ + long ArchonController::read_raw(std::string &retstring) { + const std::string function("Camera::ArchonController::read_raw"); + + long error = this->get_frame_status(); + if (error != NO_ERROR) { + logwrite(function, "ERROR getting frame status"); + retstring = "frame status query failed"; + return error; + } + + const raw_geometry_t geom = this->raw_geometry(); + if (geom.samples == 0 || geom.lines == 0) { + logwrite(function, "ERROR RAW geometry is empty; check RAW config"); + retstring = "invalid RAW geometry"; + return ERROR; + } + + const size_t fetch_bytes = static_cast(geom.blocks_per_line) * geom.lines * BLOCK_LEN; + std::shared_ptr raw_buffer(new char[fetch_bytes]); + char* bufptr = raw_buffer.get(); + + if (this->read_frame(FRAME_RAW, bufptr) != NO_ERROR) { + logwrite(function, "ERROR reading RAW frame"); + retstring = "read failed"; + return ERROR; + } + + // The Archon pads each raw line out to whole 1024-byte blocks, so copy only + // the valid RAWSAMPLES from each line into a contiguous lines x samples array + const size_t line_stride = static_cast(geom.blocks_per_line) * BLOCK_LEN; + const size_t payload_samples = static_cast(geom.lines) * geom.samples; + std::vector samples(payload_samples); + for (uint32_t line = 0; line < geom.lines; ++line) { + std::memcpy(samples.data() + static_cast(line) * geom.samples, + raw_buffer.get() + line * line_stride, + static_cast(geom.samples) * sizeof(uint16_t)); + } + + const auto index = this->frameinfo.index.load(); + const size_t payload_bytes = payload_samples * sizeof(uint16_t); + + Camera::FrameMetadata meta; + meta.frame_number = this->frameinfo.bufframen[index]; + meta.timestamp = this->frameinfo.buftimestamp[index]; + meta.width = geom.samples; + meta.height = geom.lines; + meta.bytes_per_pixel = sizeof(uint16_t); + this->interface->dispatch_frame(reinterpret_cast(samples.data()), + payload_bytes, meta); + + std::ostringstream oss; + oss << "samples=" << geom.samples << " lines=" << geom.lines << " bytes=" << payload_bytes; + retstring = oss.str(); + logwrite(function, retstring); + return NO_ERROR; + } + /***** Camera::ArchonController::read_raw *******************************/ + + /***** Camera::ArchonController::wait_for_readout ***************************/ /** * @brief creates a wait until the next completed frame buffer is ready diff --git a/camerad/archon_controller.h b/camerad/archon_controller.h index 30361b4..656f33c 100644 --- a/camerad/archon_controller.h +++ b/camerad/archon_controller.h @@ -251,10 +251,13 @@ namespace Camera { cfg_map_t configmap; param_map_t parammap; + /** @brief Archon RAW (pre-CDS) capture configuration, mirrors ACF keywords */ struct rawinfo_t { - int adchan; - uint16_t rawsamples; - uint16_t rawlines; + int adchan{0}; // RAWSEL: AD channel captured + uint16_t samples{0}; // RAWSAMPLES: 16-bit samples per line + uint16_t startline{0}; // RAWSTARTLINE + uint16_t endline{0}; // RAWENDLINE + uint16_t startpixel{0}; // RAWSTARTPIXEL } rawinfo; /** @@ -367,6 +370,21 @@ namespace Camera { long write_config_key(const char* key, const char* newvalue, bool &changed); long write_config_key(const char* key, int newvalue, bool &changed); + // RAW (pre-CDS) capture: configuration and retrieval + /** @brief resolved RAW capture geometry for the newest buffer */ + struct raw_geometry_t { + uint32_t samples; // valid 16-bit samples per line (RAWSAMPLES) + uint32_t blocks_per_line; // 1024-byte blocks per line, padded per Archon + uint32_t lines; // number of raw lines (RAWENDLINE-RAWSTARTLINE+1) + }; + + static bool is_raw_config_key(const std::string &key); + raw_geometry_t raw_geometry() const; + uint32_t raw_frame_bytes() const; // padded, size-aware byte count for a RAW fetch + long set_raw_config(const std::string &args, std::string &retstring); + long get_raw_config(std::string &retstring); + long read_raw(std::string &retstring); + std::map modemap; diff --git a/camerad/archon_interface.cpp b/camerad/archon_interface.cpp index eea554a..82a9c84 100644 --- a/camerad/archon_interface.cpp +++ b/camerad/archon_interface.cpp @@ -68,6 +68,10 @@ namespace Camera { return this->set_camera_mode(args, retstring); } else + if ( cmd == CAMERAD_RAW ) { + return this->raw(args, retstring); + } + else if ( cmd == "autofetch_mode" ) { return this->autofetch_mode(args, retstring); } @@ -955,6 +959,48 @@ namespace Camera { /***** Camera::ArchonInterface::set_vcpu_inreg ******************************/ + /***** Camera::ArchonInterface::raw ****************************************/ + /** + * @brief configure and retrieve Archon RAW (pre-CDS) data + * @param[in] args "config" | "set [...]" | "read" + * @param[out] retstring RAW configuration or retrieval summary + * @return ERROR | NO_ERROR | HELP + * + */ + long ArchonInterface::raw( const std::string args, std::string &retstring ) { + const std::string function("Camera::ArchonInterface::raw"); + + if (args=="?" || args=="help") { + retstring = CAMERAD_RAW; + retstring.append( " [ config | set [...] | read ]\n" ); + retstring.append( " config report the RAW config keywords\n" ); + retstring.append( " set .. set RAW keyword(s) then apply\n" ); + retstring.append( " read retrieve RAW data in-band as 16-bit samples\n" ); + retstring.append( " Keys: RAWENABLE RAWSEL RAWSTARTLINE RAWENDLINE RAWSTARTPIXEL RAWSAMPLES\n" ); + return HELP; + } + + std::size_t sep = args.find_first_of(" "); + const std::string subcmd = args.substr(0, sep); + const std::string subargs = (sep==std::string::npos) ? "" : args.substr(sep+1); + + if (subcmd.empty() || subcmd=="config") { + return this->controller->get_raw_config(retstring); + } + if (subcmd=="set") { + return this->controller->set_raw_config(subargs, retstring); + } + if (subcmd=="read") { + return this->controller->read_raw(retstring); + } + + logwrite(function, "ERROR unrecognized subcommand: "+subcmd); + retstring = "unrecognized subcommand: "+subcmd; + return ERROR; + } + /***** Camera::ArchonInterface::raw ****************************************/ + + /***** Camera::ArchonInterface::heater **************************************/ /** * @brief heater control: set/get enable, target, PID, ramp, ilim, input diff --git a/camerad/archon_interface.h b/camerad/archon_interface.h index 9ecf7da..dff5231 100644 --- a/camerad/archon_interface.h +++ b/camerad/archon_interface.h @@ -43,6 +43,7 @@ namespace Camera { long load_firmware( const std::string &args, std::string &retstring ) override; long native( const std::string args, std::string &retstring ) override; long power( const std::string args, std::string &retstring ) override; + long raw( const std::string args, std::string &retstring ); long test( const std::string args, std::string &retstring ) override; long do_expose() override; diff --git a/camerad/camera_interface.cpp b/camerad/camera_interface.cpp index 9e72d4f..0bace50 100644 --- a/camerad/camera_interface.cpp +++ b/camerad/camera_interface.cpp @@ -1,4 +1,5 @@ #include "camera_interface.h" +#include "frame_output_factory.h" #include #include diff --git a/camerad/camera_server.cpp b/camerad/camera_server.cpp index be6aca9..577b0cd 100644 --- a/camerad/camera_server.cpp +++ b/camerad/camera_server.cpp @@ -329,6 +329,10 @@ namespace Camera { ret = interface->controller_cmd(cmd, args, retstring); } else + if ( cmd == CAMERAD_RAW ) { + ret = interface->controller_cmd(cmd, args, retstring); + } + else if ( cmd == "bob" ) { ret = interface->controller_cmd(cmd, args, retstring); } diff --git a/common/camerad_commands.h b/common/camerad_commands.h index fdec96e..639358b 100644 --- a/common/camerad_commands.h +++ b/common/camerad_commands.h @@ -46,6 +46,7 @@ const std::string CAMERAD_OPEN("open"); const int CAMERAD_OPEN_TIMEOUT(10000); const std::string CAMERAD_PAUSE("pause"); const std::string CAMERAD_POWER("power"); const std::string CAMERAD_PREEXPOSURES("preexposures"); +const std::string CAMERAD_RAW("raw"); const std::string CAMERAD_READACF("readacf"); const std::string CAMERAD_READOUT("readout"); const std::string CAMERAD_RESUME("resume"); @@ -93,6 +94,7 @@ const std::vector CAMERAD_SYNTAX = { CAMERAD_OPEN+" [ ? | ]", CAMERAD_PAUSE, CAMERAD_PREEXPOSURES, + CAMERAD_RAW+" [ ? | config | set [...] | read ]", CAMERAD_READACF+" [ ? | ]", CAMERAD_READOUT+" [ ? ] | [ | [ ] ]", CAMERAD_RESUME,