Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Config/hispecatc.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# configuration file for camera-server
#
ARCHON_IP=10.0.0.2
ARCHON_PORT=4242
DEFAULT_FIRMWARE=/home/hsdev/camera-dev/elijahab/wdlfiles/src/hispecatc/hispecatc.acf
EXPOSE_PARAM=Expose # Archon parameter which triggers exposure
ABORT_PARAM=abort # Archon parameter to trigger an abort
READOUT_TIME=50000 # Timeout waiting for new frame (ms)
WRITE_TAPINFO_TO_FITS=yes # Tapinfo (gain, offset) be written to FITS headers {yes|no}

IMDIR=/home/hsdev/camera-dev/freedom/images # base directory to save images
BASENAME=image # base image filename

# --- Asynchronous FITS writer (frame_output_factory) ---
FITS_ENABLED=yes # enable FITS writer output {yes|no}
FITS_OUTPUT_DIR=/home/hsdev/camera-dev/freedom/images # base directory for FITS files
FITS_AUTODIR=yes # write into a YYYYMMDD subdir of FITS_OUTPUT_DIR {yes|no}
FITS_BASENAME=image # base filename for FITS files
FITS_WRITE_INTERVAL_MS=0 # 0 = write a FITS file for every exposure (no cadence gating)

DIRMODE=0077
TM_ZONE_LOG=local # time zone for log entries only, can be "UTC" or "local"
TM_ZONE=UTC # time zone for everything else, can be "UTC" or "local"
TZ_ENV=PST8PDT,M3.2.0/2,M11.1.0/2 # TZ ENV variable to use when TM_ZONE_* is "local" (follows POSIX standard)
AUTODIR=no
LONGERROR=true

NBPORT=3030 # server non-blocking port
BLKPORT=3031 # server blocking port
ASYNCGROUP=239.1.1.234 # asynchronous broadcast group (or "none")
ASYNCPORT=1234 # asynchronous message port (if ASYNCGROUP defined)
#ASYNCGROUP=none
LOGPATH=/home/hsdev/camera-dev/freedom/logs # fully qualified path to save log files
#LONGEXPOSURE=True # exptimes in seconds (not ms)
104 changes: 98 additions & 6 deletions camerad/archon_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "archon_controller.h"
#include "archon_interface.h"
#include <algorithm>

namespace Camera {

Expand Down Expand Up @@ -1035,7 +1036,6 @@ namespace Camera {
error = ERROR;
logwrite(function, "ERROR from Archon processing \""+cmd+"\"");
}
else
// First 3 bytes of reply must equal checksum else reply doesn't belong to command
if (reply.size()<3 || std::memcmp(reply.data(), check, 3) != 0) {
error = ERROR;
Expand Down Expand Up @@ -1570,6 +1570,12 @@ namespace Camera {
logwrite(function, "loaded Archon Config File OK");
this->is_firmwareloaded = true;

// now that every [MODE_*] configmap is fully populated, parse each mode's
// tapline layout into its tapinfo_t (amp name, direction, gain, offset)
for (auto &[modename, modeinfo] : this->modemap) {
this->parse_tapinfo(modeinfo);
}

// add to systemkeys keyword database
//
std::stringstream keystr;
Expand Down Expand Up @@ -1635,6 +1641,13 @@ namespace Camera {
write_config_key("PIXELCOUNT", std::to_string(mode->geometry.pixelcount).c_str(), changed);

if (changed) {
// WCONFIG only stages LINECOUNT/PIXELCOUNT into config memory; APPLYCDS
// activates the new readout geometry in the timing/CDS core without
// power-cycling the detector (unlike APPLYALL).
if (this->send_cmd(APPLYCDS) != NO_ERROR) {
logwrite(function, "ERROR applying mode geometry (APPLYCDS) to controller");
return ERROR;
}
logwrite(function, "applied mode geometry to controller");
}

Expand All @@ -1652,6 +1665,75 @@ namespace Camera {
/***** Camera::ArchonController::load_mode_settings *************************/


/***** Camera::ArchonController::parse_tapinfo *****************************/
/**
* @brief populate a mode's tapinfo_t from its TAPLINEn configmap entries
* @details Called once per mode after the ACF is parsed, so that each
* modemap entry carries its tapline layout alongside the other
* per-mode parameters (geometry, params, etc.). Each TAPLINEn is a
* string "<amp><dir>,<gain>,<offset>", e.g. "AM54L,1,0", where the
* amp name is "AM54" and the trailing L/R is the readout direction.
* An empty value ("") means that slot is not read out: it is
* skipped and does not count toward num_taps.
* @param[in,out] mode reference to the modeinfo_t to populate
*
*/
void ArchonController::parse_tapinfo(modeinfo_t &mode) {
const std::string function("Camera::ArchonController::parse_tapinfo");

mode.tapinfo.num_taps = 0;

for (int i = 0; i < 16; ++i) {
auto it = mode.configmap.find("TAPLINE" + std::to_string(i));
if (it == mode.configmap.end()) continue;

std::string val = it->second.value;
val.erase(std::remove(val.begin(), val.end(), '"'), val.end()); // drop quotes
if (val.empty()) continue; // unused tap slot

std::stringstream ss(val);
std::string name, gain, offset;
std::getline(ss, name, ',');
std::getline(ss, gain, ',');
std::getline(ss, offset, ',');

const int t = mode.tapinfo.num_taps;

// split "AM54L" into amp name "AM54" and readout direction "L"/"R"
if (!name.empty() && (name.back()=='L' || name.back()=='R')) {
mode.tapinfo.readoutdir[t] = std::string(1, name.back());
mode.tapinfo.ampname[t] = name.substr(0, name.size()-1);
} else {
mode.tapinfo.readoutdir[t].clear();
mode.tapinfo.ampname[t] = name;
}

try { mode.tapinfo.gain[t] = gain.empty() ? 1.0f : std::stof(gain); }
catch (const std::exception &) { mode.tapinfo.gain[t] = 1.0f; }
try { mode.tapinfo.offset[t] = offset.empty() ? 0.0f : std::stof(offset); }
catch (const std::exception &) { mode.tapinfo.offset[t] = 0.0f; }

++mode.tapinfo.num_taps;
}

// sanity-check against the ACF TAPLINES key, which drives controller readout
auto it = mode.configmap.find("TAPLINES");
if (it != mode.configmap.end()) {
try {
int declared = std::stoi(it->second.value);
if (declared != mode.tapinfo.num_taps) {
logwrite(function, "WARNING TAPLINES="+std::to_string(declared)+" but "
+std::to_string(mode.tapinfo.num_taps)+" non-empty taplines parsed");
}
}
catch (const std::exception &e) {
logwrite(function, "ERROR parsing TAPLINES: "+std::string(e.what()));
}
}
}
/***** Camera::ArchonController::parse_tapinfo *****************************/


/***** Camera::ArchonController::lock_buffer ********************************/
/**
* @brief lock Archon frame buffer
Expand Down Expand Up @@ -2062,10 +2144,20 @@ namespace Camera {
//
double waittime_ms = this->readout_time_msec * 1.1; // this is in msec

// if readout_time_msec was not defined or defined=0
// then do not use a timeout timer
// readout_time_msec defaults to 0 and is currently never populated, which
// would leave the loop below with no timeout and able to hang forever if a
// frame never arrives. Fall back to a timeout derived from the exposure
// time plus a generous fixed margin so acquisition can never freeze
// silently; a late/missing frame becomes a logged timeout error instead.
//
bool timeout_timer_enabled = (waittime_ms <= 0) ? false : true;
if ( waittime_ms <= 0 ) {
const double fallback_margin_ms = 10000.0; // 10 s over exposure time
waittime_ms = this->get_exptime()*1000.0 + fallback_margin_ms;
}

// the timeout timer is always enabled now that waittime_ms is guaranteed > 0
//
bool timeout_timer_enabled = true;

uint64_t start_ns = get_clock_time_nsec(); // returns nanoseconds
uint64_t timeout_ns = (uint64_t)(waittime_ms * 1e6); // convert waittime msec to nsec
Expand Down Expand Up @@ -2210,10 +2302,10 @@ namespace Camera {
<< key
<< "="
<< newvalue;

error = this->send_cmd((char*)sscmd.str().c_str()); // send the WCONFIG command here

sscmd.str(""); sscmd << key << "=" << newvalue << (error==ERROR ? "":"not") << " written";
sscmd.str(""); sscmd << key << "=" << newvalue << (error==ERROR ? " not written":" written");

if (error==NO_ERROR) {
this->configmap[key].value = newvalue; // save newvalue in the STL map
Expand Down
6 changes: 4 additions & 2 deletions camerad/archon_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@ namespace Camera {
struct tapinfo_t {
int num_taps;
int tap[16];
std::string ampname[64]; //!< amplifier name for each tap, e.g. "AM54" (from TAPLINEn in the ACF)
float gain[16];
float offset[16];
std::string readoutdir[16];
std::string readoutdir[64];
};

/**
Expand Down Expand Up @@ -294,7 +295,7 @@ namespace Camera {
std::atomic_flag archon_busy = ATOMIC_FLAG_INIT; //!< indicates a thread is accessing Archon
bool is_firmwareloaded;
std::string firmware;
int msgref;
int msgref{0}; //!< message reference id, incremented per command; MUST start initialized
std::string backplaneversion;
std::vector<int> modtype; //!< type of each module from SYSTEM command
std::vector<std::string> modversion; //!< version of each module from SYSTEM command
Expand Down Expand Up @@ -333,6 +334,7 @@ namespace Camera {
long fetchlog();
long load_acf(const std::string &filename, bool write_to_archon=true);
long load_mode_settings(modeinfo_t* mode);
void parse_tapinfo(modeinfo_t &mode); //!< populate mode.tapinfo from its TAPLINEn configmap entries
long lock_buffer(int buffernumber);
long unlock_buffer();
template <class T> void get_configmap_value(const std::string &key_in, T &value_out);
Expand Down
23 changes: 21 additions & 2 deletions utils/fits_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,31 @@ namespace Camera {
return NO_ERROR;
}

std::string FitsWriter::make_filename(uint64_t frame_number) const {
std::string FitsWriter::resolve_output_dir() {
if (!cfg_.autodir) return cfg_.output_dir;

// Date subdir (YYYYMMDD, configured time zone), matching the imdir convention
const std::string dir = cfg_.output_dir + "/" + get_system_date();
if (dir != cur_date_dir_) {
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (ec) {
logwrite("Camera::FitsWriter::resolve_output_dir",
"ERROR creating " + dir + ": " + ec.message() +
" -- falling back to " + cfg_.output_dir);
return cfg_.output_dir;
}
cur_date_dir_ = dir; // cache so we only create/log on date rollover
}
return dir;
}

std::string FitsWriter::make_filename(uint64_t frame_number) {
char num[32];
std::snprintf(num, sizeof(num), "%08llu",
static_cast<unsigned long long>(frame_number));

const std::string prefix = cfg_.output_dir + "/" + cfg_.basename + "_" + num;
const std::string prefix = resolve_output_dir() + "/" + cfg_.basename + "_" + num;
std::string filename = prefix + ".fits";

int suffix = 1;
Expand Down
9 changes: 8 additions & 1 deletion utils/fits_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace Camera {
std::string basename{"tracking"};
size_t queue_size{32};
uint32_t drain_timeout_ms{5000};
bool autodir{false}; // write into a YYYYMMDD subdir of output_dir
};

class FitsWriter : public FrameOutput {
Expand Down Expand Up @@ -59,10 +60,16 @@ namespace Camera {

void worker_loop();
long write_fits_file(const QueuedFrame &frame);
std::string make_filename(uint64_t frame_number) const;
std::string make_filename(uint64_t frame_number);
// Resolve the target directory, creating today's autodir subdir on demand.
// Worker-thread only (uses cur_date_dir_ without locking).
std::string resolve_output_dir();

FitsWriterConfig cfg_;

// Cache of the last autodir path created; touched only on the worker thread
std::string cur_date_dir_;

std::deque<QueuedFrame> queue_;
mutable std::mutex mtx_;
std::condition_variable cv_;
Expand Down
1 change: 1 addition & 0 deletions utils/frame_output_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace Camera {
else if (key == "SHM_NUM_FRAMES") out.shm_num_frames = static_cast<uint32_t>(std::stoul(val));
else if (key == "FITS_ENABLED") out.fits_enabled = parse_bool(val);
else if (key == "FITS_OUTPUT_DIR") out.fits.output_dir = val;
else if (key == "FITS_AUTODIR") out.fits.autodir = parse_bool(val);
else if (key == "FITS_BASENAME") out.fits.basename = val;
else if (key == "FITS_WRITE_INTERVAL_MS") out.fits_write_interval_ms = static_cast<uint32_t>(std::stoul(val));
else if (key == "FITS_QUEUE_SIZE") out.fits.queue_size = static_cast<size_t>(std::stoul(val));
Expand Down
2 changes: 1 addition & 1 deletion utils/shared_memory_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace Camera {
SharedMemoryWriter(const std::string &segment_name,
size_t max_frame_bytes,
uint32_t num_frames = 4);
~SharedMemoryWriter() override;
~SharedMemoryWriter();

long open() override;
long write(const char* data, size_t size, const FrameMetadata& meta) override;
Expand Down
Loading