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
26 changes: 14 additions & 12 deletions .github/workflows/emulator-integration.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@ on:
branches: [ "main" ]

jobs:
hispec-shm-emulator-test:
frame-outputs-emulator-test:
runs-on: ubuntu-latest
timeout-minutes: 10

Expand DownExpand Up@@ -52,13 +52,13 @@ jobs:
-DENABLE_SHM_OUTPUT=ON -DImageStreamIO_DIR=/usr/local/lib/cmake ..
make camerad emulator socksend shm_reader -j$(nproc)

- name: HISPEC tracking camera SHM test
- name: SHM + FITS frame outputs test
run: |
mkdir -p /tmp/ci_milk_shm
mkdir -p /tmp/ci_milk_shm /tmp/ci_fits_test

bin/emulator Config/hispec_shm_test/hispec_shm_test.cfg -i generic &
bin/emulator config/frame_outputs_test/frame_outputs_test.cfg -i generic &
sleep 2
bin/camerad --foreground --config Config/hispec_shm_test/hispec_shm_test.cfg &
bin/camerad --foreground --config config/frame_outputs_test/frame_outputs_test.cfg &
sleep 3

send() { bin/socksend -p 3131 -t 60 "$1"; }
Expand All@@ -70,10 +70,12 @@ jobs:
echo "expose -> $resp"
echo "$resp" | grep -q "DONE" || exit 1

# DONE only means camerad accepted the command, not that SharedMemoryWriter
# succeeded -- shm_reader's exit code is the real pass/fail signal
# DONE only means camerad accepted the command, not that the writers
# succeeded -- shm_reader's exit code and the FITS file's existence
# are the real pass/fail signal
sleep 1
bin/shm_reader ci_hispec_shm /tmp/ci_milk_shm
bin/shm_reader ci_frame_outputs_shm /tmp/ci_milk_shm
ls /tmp/ci_fits_test/ci_frame_outputs_*.fits

pkill -f 'bin/camerad' || true
pkill -f 'bin/emulator' || true
Expand DownExpand Up@@ -114,9 +116,9 @@ jobs:

- name: CryoScope synthetic test
run: |
bin/emulator Config/cryoscope/cryoscope.cfg -i generic &
bin/emulator config/cryoscope/cryoscope.cfg -i generic &
sleep 2
bin/camerad --foreground --config Config/cryoscope/cryoscope.cfg &
bin/camerad --foreground --config config/cryoscope/cryoscope.cfg &
sleep 3

send() { bin/socksend -p 3031 -t 60 "$1"; }
Expand All@@ -135,8 +137,8 @@ jobs:

- name: CryoScope FITS playback test
run: |
cp Config/cryoscope/cryoscope.cfg /tmp/cryoscope_fits_test.cfg
echo "EMULATOR_DATADIR=Config/cryoscope" >> /tmp/cryoscope_fits_test.cfg
cp config/cryoscope/cryoscope.cfg /tmp/cryoscope_fits_test.cfg
echo "EMULATOR_DATADIR=config/cryoscope" >> /tmp/cryoscope_fits_test.cfg

bin/emulator /tmp/cryoscope_fits_test.cfg -i generic &
sleep 2
Expand Down
16 changes: 0 additions & 16 deletions Config/cameraserver.cfg

This file was deleted.

30 changes: 0 additions & 30 deletions Config/hispec_shm_test/hispec_shm_test.cfg

This file was deleted.

34 changes: 0 additions & 34 deletions Config/hispecatc.cfg

This file was deleted.

4 changes: 2 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,7 +77,7 @@ If you encounter any problems or have questions about this project, please open
$ ../bin/camerad -d <file.cfg>
```

*Replace `<file.cfg>` with an appropriate configuration file. See the example `.cfg` files in the `Config` and `Config/demo` directories.*
*Replace `<file.cfg>` with an appropriate configuration file. See the example `.cfg` files in the `config` directory (per-instrument deployment configs live in each instrument's own repo under its `config/` directory; `config/demo` here is a generic example).*

6. **(Optional) Run the Archon Emulator:**

Expand All@@ -95,7 +95,7 @@ If you encounter any problems or have questions about this project, please open

## Frame Outputs

Instruments that opt in publish each acquired frame to one or more outputs, configured via `.cfg` file keys read by `Camera::apply_config_overrides()`. Both outputs are independent; eitheror both can be enabled.
Every instrument publishes each acquired frame to one or more outputs, configured entirely via `.cfg` file keys (`Camera::Interface::configure_frame_outputs()` builds them from `Camera::apply_config_overrides()`, called once at startup for every instrument, not just HISPEC). Both outputs are independent; either, both, or neither can be enabled per instrument.

### FITS

Expand Down
15 changes: 15 additions & 0 deletions camerad/camera_interface.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -99,4 +99,19 @@ namespace Camera {
return NO_ERROR;
}
/***** Camera::Interface::datacube *********************************************/


/***** Camera::Interface::configure_frame_outputs *******************************/
/**
* @brief build frame_outputs (SHM/FITS) from the config file
* @details Called unconditionally from camerad.cpp after configure_instrument(),
* so no derived override can silently skip wiring frame_outputs.
*
*/
void Interface::configure_frame_outputs() {
Camera::FrameOutputsConfig fo_cfg;
Camera::apply_config_overrides(fo_cfg, this->configfile);
this->frame_outputs = Camera::make_frame_outputs(fo_cfg);
}
/***** Camera::Interface::configure_frame_outputs *******************************/
}
2 changes: 2 additions & 0 deletions camerad/camera_interface.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,7 @@
#include "camerad_commands.h"
#include "exposure_modes.h"
#include "frame_output.h"
#include "frame_output_factory.h"

#include <memory>
#include <vector>
Expand DownExpand Up@@ -84,6 +85,7 @@ namespace Camera {
void disconnect_controller();
long key(std::string args, std::string &retstring);
long datacube(std::string args, std::string &retstring);
void configure_frame_outputs();
bool is_exposuremode_set() { return ( this->exposuremode && !this->exposuremode->get_type().empty() ); }

void set_abortstate() { this->abortstate.store(true, std::memory_order_seq_cst); }
Expand Down
1 change: 1 addition & 0 deletions camerad/camerad.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ int main( int argc, char** argv ) {
camerad.interface->configure_controller();
camerad.interface->configure_interface();
camerad.interface->configure_instrument();
camerad.interface->configure_frame_outputs();
}
catch (const std::exception &e) {
logwrite(function, "ERROR configuring system: "+std::string(e.what()));
Expand Down
File renamed without changes.
File renamed without changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,14 +12,14 @@ TZ_ENV=PST8PDT,M3.2.0/2,M11.1.0/2
NBPORT=3030
BLKPORT=3031
EMULATOR_PORT=3032
EMULATOR_SYSTEM=Config/demo/demo.system
EMULATOR_SYSTEM=config/demo/demo.system

ASYNCGROUP=239.1.1.234
ASYNCPORT=1234

ARCHON_IP=localhost
ARCHON_PORT=3032
DEFAULT_FIRMWARE=Config/cryoscope/cryoscope.acf
DEFAULT_FIRMWARE=config/cryoscope/cryoscope.acf
ABORT_PARAM=Abort
EXPOSE_PARAM=Expose
EXPTIME_MSEC_PARAM=exptime
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions Config/demo/demo.cfg → config/demo/demo.cfg
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,7 @@ LONGERROR=true
NBPORT=3030 # server non-blocking port
BLKPORT=3031 # 3031 or 3041 blocking port
EMULATOR_PORT=3032
EMULATOR_SYSTEM=Config/demo/demo.system
EMULATOR_SYSTEM=config/demo/demo.system
#
# If you have a firewall, you must allow
# multicast address 239.192.13.165, ports 1304-1305.
Expand All@@ -28,7 +28,7 @@ ASYNCPORT=1234 # asynchronous message port
# the following should not be changed
ARCHON_IP=localhost
ARCHON_PORT=3032
DEFAULT_FIRMWARE=Config/demo/demo.acf
DEFAULT_FIRMWARE=config/demo/demo.acf
ABORT_PARAM=abort # Archon parameter to trigger an abort
EXPOSE_PARAM=Expose # Archon parameter to trigger exposure
EXPTIME_MSEC_PARAM=exptime # Archon parameter for exposure time in msec
Expand Down
File renamed without changes.
38 changes: 38 additions & 0 deletions config/frame_outputs_test/frame_outputs_test.cfg
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
# camerad + emulator SHM/FITS frame_outputs testing (CI). Exercises the
# generic frame_outputs mechanism (Camera::Interface::configure_frame_outputs),
# not anything hispec_tracking_camera-specific; hispec_tracking_camera is
# just the build's INSTRUMENT since it's the only fully-working one today.

DAEMON=no
IMDIR=/tmp
LOGPATH=/tmp
BASENAME=frameoutputstest
DIRMODE=0077
TM_ZONE_LOG=local
TM_ZONE=UTC
TZ_ENV=PST8PDT,M3.2.0/2,M11.1.0/2

NBPORT=3130
BLKPORT=3131
EMULATOR_PORT=3132
EMULATOR_SYSTEM=config/frame_outputs_test/frame_outputs_test.system

ASYNCGROUP=none

ARCHON_IP=localhost
ARCHON_PORT=3132
DEFAULT_FIRMWARE=camerad/Instruments/hispec_tracking_camera/config/hispecatc.acf
EXPOSE_PARAM=Expose
ABORT_PARAM=abort
READOUT_TIME=5000
WRITE_TAPINFO_TO_FITS=no

SHM_ENABLED=yes
SHM_SEGMENT_NAME=ci_frame_outputs_shm
SHM_RING_BUFFER_SIZE=2
SHM_DIR=/tmp/ci_milk_shm

FITS_ENABLED=yes
FITS_OUTPUT_DIR=/tmp/ci_fits_test
FITS_AUTODIR=no
FITS_BASENAME=ci_frame_outputs
22 changes: 8 additions & 14 deletions utils/frame_output_factory.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,22 +52,16 @@ namespace Camera {

if (cfg.shm_enabled) {
#ifdef CAMERAD_HAVE_SHM
if (cfg.shm_max_frame_bytes == 0) {
logwrite(function, "WARNING shm_enabled but shm_max_frame_bytes==0; SHM skipped");
auto shm = std::make_unique<SharedMemoryWriter>(
cfg.shm_segment_name, cfg.shm_ring_buffer_size, cfg.shm_dir);
if (shm->open() == NO_ERROR) {
logwrite(function, "SHM output enabled: segment=" + cfg.shm_segment_name +
" ring_buffer_size=" + std::to_string(cfg.shm_ring_buffer_size) +
" dir=" + (cfg.shm_dir.empty() ? "(default)" : cfg.shm_dir));
outputs.push_back(std::move(shm));
}
else {
auto shm = std::make_unique<SharedMemoryWriter>(
cfg.shm_segment_name, cfg.shm_max_frame_bytes, cfg.shm_ring_buffer_size, cfg.shm_dir);
if (shm->open() == NO_ERROR) {
logwrite(function, "SHM output enabled: segment=" + cfg.shm_segment_name +
" max_bytes=" + std::to_string(cfg.shm_max_frame_bytes) +
" ring_buffer_size=" + std::to_string(cfg.shm_ring_buffer_size) +
" dir=" + (cfg.shm_dir.empty() ? "(default)" : cfg.shm_dir));
outputs.push_back(std::move(shm));
}
else {
logwrite(function, "WARNING SHM output failed to open; skipped");
}
logwrite(function, "WARNING SHM output failed to open; skipped");
}
#else
logwrite(function, "WARNING shm_enabled but this build was compiled without SHM support (ENABLE_SHM_OUTPUT=OFF)");
Expand Down
1 change: 0 additions & 1 deletion utils/frame_output_factory.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ namespace Camera {
struct FrameOutputsConfig {
bool shm_enabled{false};
std::string shm_segment_name{"camera"};
size_t shm_max_frame_bytes{0}; // required > 0 when shm_enabled
uint32_t shm_ring_buffer_size{4}; // depth of ImageStreamIO's internal history ring buffer
std::string shm_dir{}; // ImageStreamIO base directory; empty uses its own default resolution

Expand Down
14 changes: 1 addition & 13 deletions utils/shared_memory_writer.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,11 +41,9 @@ namespace {
namespace Camera {

SharedMemoryWriter::SharedMemoryWriter(const std::string &segment_name,
size_t max_frame_bytes,
uint32_t ring_buffer_size,
const std::string &shm_dir)
: segment_name_(segment_name),
max_frame_bytes_(max_frame_bytes),
ring_buffer_size_(ring_buffer_size),
shm_dir_(shm_dir) {
}
Expand All@@ -61,10 +59,6 @@ namespace Camera {
logwrite(function, "ERROR segment name is empty");
return ERROR;
}
if (max_frame_bytes_ == 0) {
logwrite(function, "ERROR max_frame_bytes must be > 0");
return ERROR;
}
if (ring_buffer_size_ == 0) {
logwrite(function, "ERROR ring_buffer_size must be > 0");
return ERROR;
Expand All@@ -83,8 +77,7 @@ namespace Camera {
opened_ = true;

// Geometry is fixed for a stream's whole life, so create happens in write(), not here
logwrite(function, "ready to publish \"" + segment_name_ + "\" (max " +
std::to_string(max_frame_bytes_) + " bytes/frame, " +
logwrite(function, "ready to publish \"" + segment_name_ + "\" (" +
std::to_string(ring_buffer_size_) + " frames, dir=" +
(shm_dir_.empty() ? "(default)" : shm_dir_) + ")");
return NO_ERROR;
Expand All@@ -109,11 +102,6 @@ namespace Camera {

const size_t frame_bytes =
static_cast<size_t>(meta.width) * meta.height * meta.bytes_per_pixel;
if (frame_bytes > max_frame_bytes_) {
logwrite(function, "ERROR frame size " + std::to_string(frame_bytes) +
" exceeds max " + std::to_string(max_frame_bytes_));
return ERROR;
}
if (size < frame_bytes) {
logwrite(function, "ERROR frame data " + std::to_string(size) +
" < expected " + std::to_string(frame_bytes));
Expand Down
2 changes: 0 additions & 2 deletions utils/shared_memory_writer.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,6 @@ namespace Camera {
class SharedMemoryWriter : public FrameOutput {
public:
SharedMemoryWriter(const std::string &segment_name,
size_t max_frame_bytes,
uint32_t ring_buffer_size = 4,
const std::string &shm_dir = "");
~SharedMemoryWriter();
Expand All@@ -30,7 +29,6 @@ namespace Camera {

private:
std::string segment_name_;
size_t max_frame_bytes_;
uint32_t ring_buffer_size_;
std::string shm_dir_;
bool opened_{false};
Expand Down
Loading