12#include <mx/improc/eigenImage.hpp>
13#include <mx/improc/milkImage.hpp>
14#include <mx/ioutils/fits/fitsFile.hpp>
25template <
typename typeT>
34 return _DATATYPE_FLOAT;
40 return _DATATYPE_DOUBLE;
88template <
class derivedT,
typename realT>
197 mx::improc::milkImage<realT>
612 const pcf::IndiProperty &
ipRecv
632 const pcf::IndiProperty &
ipRecv
642 const pcf::IndiProperty &
ipRecv
652 const pcf::IndiProperty &
ipRecv
672 const pcf::IndiProperty &
ipRecv
682 const pcf::IndiProperty &
ipRecv );
691 const pcf::IndiProperty &
ipRecv
700 const pcf::IndiProperty &
ipRecv );
709 const pcf::IndiProperty &
ipRecv
718 const pcf::IndiProperty &
ipRecv );
727 const pcf::IndiProperty &
ipRecv
747 const pcf::IndiProperty &
ipRecv
756 const pcf::IndiProperty &
ipRecv );
773 typedef int32_t cbIndexT;
775 double m_t0{ 0 }, m_tf{ 0 }, m_tsat0{ 0 }, m_tsatf{ 0 };
776 double m_tact0{ 0 }, m_tact1{ 0 }, m_tact2{ 0 }, m_tact3{ 0 }, m_tact4{ 0 };
777 double m_tdelta0 {0}, m_tdeltaf {0};
779 mx::sigproc::circularBufferIndex<double, cbIndexT> m_piTimes;
781 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satSem;
783 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actProc;
785 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actCom;
787 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satUp;
789 mx::sigproc::circularBufferIndex<double, cbIndexT> m_deltaUp;
792 std::vector<double> m_piTimesD;
793 std::vector<double> m_satSemD;
794 std::vector<double> m_actProcD;
795 std::vector<double> m_actComD;
796 std::vector<double> m_satUpD;
797 std::vector<double> m_deltaUpD;
805 return *
static_cast<derivedT *
>( this );
809template <
class derivedT,
typename realT>
812 for(
auto &mi : m_channels )
821template <
class derivedT,
typename realT>
827template <
class derivedT,
typename realT>
833template <
class derivedT,
typename realT>
839template <
class derivedT,
typename realT>
842 return m_flatDefault;
845template <
class derivedT,
typename realT>
848 return m_testDefault;
851template <
class derivedT,
typename realT>
857template <
class derivedT,
typename realT>
863template <
class derivedT,
typename realT>
869template <
class derivedT,
typename realT>
872 return m_shmimSatPerc;
875template <
class derivedT,
typename realT>
881template <
class derivedT,
typename realT>
887template <
class derivedT,
typename realT>
893template <
class derivedT,
typename realT>
899template <
class derivedT,
typename realT>
905template <
class derivedT,
typename realT>
908 return m_percThreshold;
911template <
class derivedT,
typename realT>
914 return m_intervalSatThreshold;
917template <
class derivedT,
typename realT>
920 return m_intervalSatCountThreshold;
923template <
class derivedT,
typename realT>
926 return m_satTriggerDevice;
929template <
class derivedT,
typename realT>
932 return m_satTriggerProperty;
935template <
class derivedT,
typename realT>
938 return m_calibRelDir;
941template <
class derivedT,
typename realT>
944 return m_numChannels;
947template <
class derivedT,
typename realT>
953template <
class derivedT,
typename realT>
956 return m_accumSatMap;
959template <
class derivedT,
typename realT>
965template <
class derivedT,
typename realT>
968 return m_deltaChannels;
971template <
class derivedT,
typename realT>
977template <
class derivedT,
typename realT>
983template <
class derivedT,
typename realT>
986 config.add(
"dm.calibPath",
994 "The path to calibration files, relative to the MagAO-X calibration path." );
996 config.add(
"dm.flatPath",
1004 "The path to flat files. Default is the calibration path." );
1006 config.add(
"dm.flatDefault",
1014 "The default flat file (path and extension are not required)." );
1016 config.add(
"dm.testPath",
1024 "The path to test files. Default is the calibration path plus /tests." );
1026 config.add(
"dm.testDefault",
1034 "The default test file (path and extension are not required)." );
1036 config.add(
"dm.actMaskPath",
1044 "The path to the actuator mask for this DM, relative to the calib path." );
1049 config.add(
"dm.threadPrio",
1057 "The real-time priority of the dm control thread." );
1059 config.add(
"dm.cpuset",
1067 "The cpuset for the dm control thread." );
1069 config.add(
"dm.shmimName",
1077 "The name of the ImageStreamIO shared memory image to monitor for DM comands. Will be used as "
1078 "/tmp/<shmimName>.im.shm." );
1082 config.add(
"dm.shmimFlat",
1090 "The name of the ImageStreamIO shared memory image to write the flat command to. Default is shmimName "
1091 "with 00 apended (i.e. dm00disp -> dm00disp00). " );
1093 config.add(
"dm.shmimTest",
1101 "The name of the ImageStreamIO shared memory image to write the test command to. Default is shmimName "
1102 "with 01 apended (i.e. dm00disp -> dm00disp01). " );
1104 config.add(
"dm.shmimSat",
1112 "The name of the ImageStreamIO shared memory image to write the saturation map to. Default is "
1113 "shmimName with SA apended (i.e. dm00disp -> dm00dispSA). This is created." );
1115 config.add(
"dm.shmimSatPerc",
1123 "The name of the ImageStreamIO shared memory image to write the saturation percentage map to. Default "
1124 "is shmimName with SP apended (i.e. dm00disp -> dm00dispSP). This is created." );
1126 config.add(
"dm.satAvgInt",
1134 "The interval in milliseconds over which saturation "
1135 "is accumulated before updating. Default is 100 ms." );
1137 config.add(
"dm.satThreadPrio",
1145 "The priority for the saturation thread. "
1146 "Usually ok to be 0." );
1148 config.add(
"dm.shmimShape",
1156 "The name of the ImageStreamIO shared memory image to write the desaturated shape to. Default is "
1157 "shmimName with _shape apended (i.e. dm00disp -> dm00disp_shape). This is created." );
1159 config.add(
"dm.shmimDelta",
1167 "The name of the ImageStreamIO shared memory image to write the "
1168 "desaturated delta-shape to. Default is "
1169 "shmimName with _delta apended (i.e. dm00disp -> dm00disp_delta). This is created." );
1171 config.add(
"dm.deltaChannels",
1179 "The names of the DM channels which are delta commands to be excluded from the total flat." );
1181 config.add(
"dm.width",
1189 "The width of the DM in actuators." );
1191 config.add(
"dm.height",
1199 "The height of the DM in actuators." );
1201 config.add(
"dm.percThreshold",
1209 "Threshold on percentage of frames an actuator is saturated over an interval. Default is 0.98." );
1211 config.add(
"dm.intervalSatThreshold",
1213 "dm.intervalSatThreshold",
1216 "intervalSatThreshold",
1219 "Threshold on percentage of actuators which exceed percThreshold in an interval. Default is 0.5." );
1221 config.add(
"dm.intervalSatCountThreshold",
1223 "dm.intervalSatCountThreshold",
1226 "intervalSatCountThreshold",
1229 "Threshold on number of consecutive intervals the intervalSatThreshold is exceeded. Default is 10." );
1231 config.add(
"dm.satTriggerDevice",
1233 "dm.satTriggerDevice",
1239 "Device(s) with a toggle switch to toggle on saturation trigger." );
1241 config.add(
"dm.satTriggerProperty",
1243 "dm.satTriggerProperty",
1246 "satTriggerProperty",
1249 "Property with a toggle switch to toggle on saturation trigger, one per entry in satTriggerDevice." );
1254template <
class derivedT,
typename realT>
1258 m_calibPath = derived().m_calibDir +
"/" + m_calibRelDir;
1259 config( m_calibPath,
"dm.calibPath" );
1262 m_flatPath = m_calibPath +
"/flats";
1263 config( m_flatPath,
"dm.flatPath" );
1265 config( m_flatDefault,
"dm.flatDefault" );
1266 if( m_flatDefault !=
"" )
1268 m_flatDefault = mx::ioutils::pathStem( m_flatDefault );
1269 m_flatCurrent =
"default";
1273 m_testPath = m_calibPath +
"/tests";
1274 config( m_testPath,
"dm.testPath" );
1276 config( m_testDefault,
"dm.testDefault" );
1277 if( m_testDefault !=
"" )
1279 m_testDefault = mx::ioutils::pathStem( m_testDefault );
1280 m_testCurrent =
"default";
1283 config( m_actMaskPath,
"dm.actMaskPath" );
1287 config( derived().m_smThreadPrio,
"dm.threadPrio" );
1288 config( derived().m_smCpuset,
"dm.cpuset" );
1290 config( derived().m_shmimName,
"dm.shmimName" );
1292 derived().m_getExistingFirst =
true;
1295 if( derived().m_shmimName !=
"" )
1297 m_shmimFlat = derived().m_shmimName +
"00";
1298 config( m_shmimFlat,
"dm.shmimFlat" );
1300 m_shmimTest = derived().m_shmimName +
"02";
1301 config( m_shmimTest,
"dm.shmimTest" );
1303 m_shmimSat = derived().m_shmimName +
"ST";
1304 config( m_shmimSat,
"dm.shmimSat" );
1306 m_shmimSatPerc = derived().m_shmimName +
"SP";
1307 config( m_shmimSatPerc,
"dm.shmimSatPerc" );
1309 config( m_satAvgInt,
"dm.satAvgInt" );
1311 config( m_satThreadPrio,
"dm.satSatThreadPrio" );
1313 m_shmimShape = derived().m_shmimName +
"_shape";
1314 config( m_shmimShape,
"dm.shmimShape" );
1316 m_shmimDelta = derived().m_shmimName +
"_delta";
1317 config( m_shmimDelta,
"dm.shmimDelta" );
1319 m_shmimDiff = derived().m_shmimName +
"_diff";
1320 config( m_shmimDiff,
"dm.shmimDiff" );
1322 config( m_deltaChannels,
"dm.deltaChannels" );
1327 config.isSet(
"dm.shmimFlat" );
1328 config.isSet(
"dm.shmimTest" );
1329 config.isSet(
"dm.shmimSat" );
1330 config.isSet(
"dm.shmimSatPerc" );
1331 config.isSet(
"dm.satAvgInt" );
1332 config.isSet(
"dm.shmimShape" );
1333 config.isSet(
"dm.shmimDelta" );
1334 config.isSet(
"dm.deltaChannels" );
1337 config( m_dmWidth,
"dm.width" );
1338 config( m_dmHeight,
"dm.height" );
1340 config( m_percThreshold,
"dm.percThreshold" );
1341 config( m_intervalSatThreshold,
"dm.intervalSatThreshold" );
1342 config( m_intervalSatCountThreshold,
"dm.intervalSatCountThreshold" );
1343 config( m_satTriggerDevice,
"dm.satTriggerDevice" );
1344 config( m_satTriggerProperty,
"dm.satTriggerProperty" );
1346 if( m_dmWidth > 0 && m_dmHeight > 0 )
1350 m_actMask.create( derived().m_shmimName +
"_actmask", m_dmWidth, m_dmHeight );
1352 catch(
const std::exception &e )
1354 derivedT::template log<text_log>( std::format(
"exception caught creating actuator mask: "
1356 derived().m_shmimName +
"_actmask",
1358 logPrio::LOG_ERROR ) );
1362 if( m_actMaskPath !=
"" )
1364 mx::improc::eigenImage<realT> actMask;
1366 mx::fits::fitsFile<realT> ff;
1368 mx::error_t errc = ff.read( actMask, m_calibPath +
'/' + m_actMaskPath );
1370 if( errc != mx::error_t::noerror )
1372 derivedT::template log<text_log>( std::format(
"error reading actuator mask file {}: "
1374 m_calibPath +
'/' + m_actMaskPath,
1375 mx::errorMessage( errc ),
1376 mx::errorName( errc ) ),
1377 logPrio::LOG_ERROR );
1381 if( actMask.rows() != m_dmWidth || actMask.cols() != m_dmHeight )
1383 derivedT::template log<text_log>( std::format(
"actuaor mask {}x{} is not same size as flag {}x{}",
1388 logPrio::LOG_ERROR );
1393 m_actMask = actMask;
1397 m_actMask().setConstant( 1.0 );
1404template <
class derivedT,
typename realT>
1407 if( m_dmDataType == 0 )
1409 derivedT::template log<software_error>( {
"unsupported DM data type" } );
1418 m_indiP_flatShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1419 m_indiP_flatShmim.setDevice( derived().configName() );
1420 m_indiP_flatShmim.setName(
"flat_shmim" );
1421 m_indiP_flatShmim.setPerm( pcf::IndiProperty::ReadOnly );
1422 m_indiP_flatShmim.setState( pcf::IndiProperty::Idle );
1423 m_indiP_flatShmim.add( pcf::IndiElement(
"channel" ) );
1424 m_indiP_flatShmim[
"channel"] = m_shmimFlat;
1426 if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim ) < 0 )
1428#ifndef DM_TEST_NOLOG
1429 derivedT::template log<software_error>( {
"" } );
1435 derived().createStandardIndiToggleSw( m_indiP_setFlat,
"flat_set" );
1436 if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat ) < 0 )
1438#ifndef DM_TEST_NOLOG
1439 derivedT::template log<software_error>( {
"" } );
1449 m_indiP_testShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1450 m_indiP_testShmim.setDevice( derived().configName() );
1451 m_indiP_testShmim.setName(
"test_shmim" );
1452 m_indiP_testShmim.setPerm( pcf::IndiProperty::ReadOnly );
1453 m_indiP_testShmim.setState( pcf::IndiProperty::Idle );
1454 m_indiP_testShmim.add( pcf::IndiElement(
"channel" ) );
1455 m_indiP_testShmim[
"channel"] = m_shmimTest;
1456 derived().createStandardIndiToggleSw( m_indiP_setTest,
"test_shmim" );
1457 if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim ) < 0 )
1459#ifndef DM_TEST_NOLOG
1460 derivedT::template log<software_error>( {
"" } );
1466 derived().createStandardIndiToggleSw( m_indiP_setTest,
"test_set" );
1467 if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest ) < 0 )
1469#ifndef DM_TEST_NOLOG
1470 derivedT::template log<software_error>( {
"" } );
1476 derived().createStandardIndiRequestSw( m_indiP_init,
"initDM" );
1477 if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init ) < 0 )
1480 #ifndef DM_TEST_NOLOG
1481 derivedT::template log<software_error>( {
""} );
1489 derived().createStandardIndiRequestSw( m_indiP_zero,
"zeroDM" );
1491 if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero ) < 0 )
1494 #ifndef DM_TEST_NOLOG
1495 derivedT::template log<software_error>( {
""} );
1503 derived().createStandardIndiRequestSw( m_indiP_release,
"releaseDM" );
1504 if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release ) < 0 )
1506 return derivedT::template log<software_error, -1>( {
"" } );
1509 derived().createStandardIndiRequestSw( m_indiP_zeroAll,
"zeroAll" );
1510 if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll ) < 0 )
1512#ifndef DM_TEST_NOLOG
1513 derivedT::template log<software_error>( {
"" } );
1518 if( m_flatDefault !=
"" )
1520 loadFlat(
"default" );
1523 if( m_testDefault !=
"" )
1525 loadTest(
"default" );
1528 if( sem_init( &m_satSemaphore, 0, 0 ) < 0 )
1530 return derivedT::template log<software_critical, -1>( { errno, 0,
"Initializing sat semaphore" } );
1533 if( derived().threadStart( m_satThread,
1541 satThreadStart ) < 0 )
1543 derivedT::template log<software_error, -1>( {
"" } );
1550template <
class derivedT,
typename realT>
1554 if( pthread_tryjoin_np( m_satThread.native_handle(), 0 ) == 0 )
1556 derivedT::template log<software_error>( {
"saturation thread has exited" } );
1565 if( m_intervalSatTrip )
1568 m_intervalSatTrip =
false;
1572 static uint64_t lastMono = 0;
1574 if( m_piTimes.size() >= m_piTimes.maxEntries() && m_piTimes.maxEntries() > 0 && m_piTimes.mono() != lastMono )
1576 cbIndexT refEntry = m_piTimes.earliest();
1578 m_piTimesD.resize( m_piTimes.maxEntries() );
1579 m_satSemD.resize( m_satSem.maxEntries() );
1580 m_actProcD.resize( m_actProc.maxEntries() );
1581 m_actComD.resize( m_actCom.maxEntries() );
1582 m_satUpD.resize( m_satUp.maxEntries() );
1583 m_deltaUpD.resize( m_deltaUp.maxEntries() );
1585 for(
size_t n = 0; n < m_piTimesD.size(); ++n )
1587 m_piTimesD[n] = m_piTimes.at( refEntry, n );
1588 m_satSemD[n] = m_satSem.at( refEntry, n );
1589 m_actProcD[n] = m_actProc.at( refEntry, n );
1590 m_actComD[n] = m_actCom.at( refEntry, n );
1591 m_satUpD[n] = m_satUp.at( refEntry, n );
1592 m_deltaUpD[n] = m_deltaUp.at( refEntry, n );
1595 std::cerr <<
"Act. Process: " << mx::math::vectorMean( m_actProcD ) <<
" +/- "
1596 << sqrt( mx::math::vectorVariance( m_actProcD ) ) <<
"\n";
1597 std::cerr <<
"Act. Command: " << mx::math::vectorMean( m_actComD ) <<
" +/- "
1598 << sqrt( mx::math::vectorVariance( m_actComD ) ) <<
"\n";
1599 std::cerr <<
"Sat. Update: " << mx::math::vectorMean( m_satUpD ) <<
" +/- "
1600 << sqrt( mx::math::vectorVariance( m_satUpD ) ) <<
"\n";
1601 std::cerr <<
"Delta Update: " << mx::math::vectorMean( m_deltaUpD ) <<
" +/- "
1602 << sqrt( mx::math::vectorVariance( m_deltaUpD ) ) <<
"\n";
1603 std::cerr <<
"Tot. CommandDM: " << mx::math::vectorMean( m_piTimesD ) <<
" +/- "
1604 << sqrt( mx::math::vectorVariance( m_piTimesD ) ) <<
"\n";
1605 std::cerr <<
"Sat. Semaphore: " << mx::math::vectorMean( m_satSemD ) <<
" +/- "
1606 << sqrt( mx::math::vectorVariance( m_satSemD ) ) <<
"\n";
1609 lastMono = m_piTimes.mono();
1616template <
class derivedT,
typename realT>
1619 if( m_satThread.joinable() )
1621 pthread_kill( m_satThread.native_handle(), SIGUSR1 );
1634template <
class derivedT,
typename realT>
1642template <
class derivedT,
typename realT>
1651template <
class derivedT,
typename realT>
1654 std::string milkShmimDir = mx::sys::getEnv(
"MILK_SHM_DIR" );
1655 if( milkShmimDir ==
"" )
1657 milkShmimDir =
"/milk/shm";
1660 std::vector<std::string> dmlist;
1661 mx::error_t errc = mx::ioutils::getFileNames( dmlist, milkShmimDir, derived().m_shmimName,
".im",
".shm" );
1663 mx_error_check_rv( errc, -1 );
1665 if( dmlist.size() == 0 )
1667 derivedT::template log<software_error>( {
"no dm channels found for " + derived().m_shmimName } );
1673 for(
size_t n = 0; n < dmlist.size(); ++n )
1676 snprintf( nstr,
sizeof( nstr ),
"%02d.im.shm", (
int)n );
1677 std::string tgt = derived().m_shmimName;
1680 for(
size_t m = 0; m < dmlist.size(); ++m )
1682 if( dmlist[m].find( tgt ) != std::string::npos )
1684 if( (
int)n > m_numChannels )
1694 derivedT::template log<text_log>(
1695 { std::format(
"Found {} chanels for {} ", m_numChannels, derived().m_shmimName ) } );
1697 m_channels.resize( m_numChannels,
nullptr );
1699 m_notDeltas.clear();
1702 for(
size_t n = 0; n < m_channels.size(); ++n )
1704 std::string sname = std::format(
"{}{:02}", derived().m_shmimName, n );
1708 m_channels[n] =
new mx::improc::milkImage<realT>( sname );
1710 catch(
const std::exception &e )
1712 derivedT::template log<software_error>( {
"exception opening " + sname +
": " + e.what() } );
1715 std::cerr <<
"looking for " << sname <<
'\n';
1716 auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
1717 if( res == m_deltaChannels.end() )
1719 std::cerr <<
" not a delta\n";
1720 m_notDeltas.push_back( n );
1724 std::cerr <<
" is a delta\n";
1725 m_deltas.push_back( n );
1729 std::cerr <<
"not deltas: ";
1730 for(
size_t n = 0; n < m_notDeltas.size(); ++n )
1732 std::cerr << m_notDeltas[n] <<
' ';
1736 std::cerr <<
"deltas: ";
1737 for(
size_t n = 0; n < m_deltas.size(); ++n )
1739 std::cerr << m_deltas[n] <<
' ';
1746template <
class derivedT,
typename realT>
1749 static_cast<void>( sp );
1753 if( derived().m_width != m_dmWidth )
1755 derivedT::template log<software_critical>( {
"shmim width does not match configured DM width" } );
1759 if( derived().m_height != m_dmHeight )
1761 derivedT::template log<software_critical>( {
"shmim height does not match configured DM height" } );
1765 if( derived().m_dataType != m_dmDataType )
1767 derivedT::template log<software_critical>( {
"shmim data type does not match configured DM data type" } );
1776 m_instSatMap.resize( m_dmWidth, m_dmHeight );
1777 m_instSatMap.setZero();
1779 m_accumSatMap.resize( m_dmWidth, m_dmHeight );
1780 m_accumSatMap.setZero();
1782 m_satPercMap.resize( m_dmWidth, m_dmHeight );
1783 m_satPercMap.setZero();
1785 if( findDMChannels() < 0 )
1787 derivedT::template log<software_critical>( {
"error finding DM channels" } );
1794 m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
1795 m_outputShape().setZero();
1797 catch(
const std::exception &e )
1799 return derivedT::template log<software_error, -1>(
1800 { std::string(
"creating output shape shmim: " ) + e.what() } );
1805 m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
1806 m_outputDelta().setZero();
1808 catch(
const std::exception &e )
1810 return derivedT::template log<software_error, -1>(
1811 { std::string(
"creating output delta shmim: " ) + e.what() } );
1816 m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
1817 m_outputDiff().setZero();
1819 catch(
const std::exception &e )
1821 return derivedT::template log<software_error, -1>(
1822 { std::string(
"creating output diff shmim: " ) + e.what() } );
1825 m_totalFlat.resize( m_dmWidth, m_dmHeight );
1826 m_totalFlat.setZero();
1828 m_totalDelta.resize( m_dmWidth, m_dmHeight );
1829 m_totalDelta.setZero();
1832 #ifdef XWC_DMTIMINGS
1833 m_piTimes.maxEntries( 2000 );
1834 m_satSem.maxEntries( 2000 );
1835 m_actProc.maxEntries( 2000 );
1836 m_actCom.maxEntries( 2000 );
1837 m_satUp.maxEntries( 2000 );
1838 m_deltaUp.maxEntries( 2000 );
1844template <
class derivedT,
typename realT>
1847 static_cast<void>( sp );
1850 #ifdef XWC_DMTIMINGS
1851 m_t0 = mx::sys::get_curr_time();
1854 int rv = derived().commandDM( curr_src );
1858 derivedT::template log<software_critical>( { errno, rv,
"Error from commandDM" } );
1863 #ifdef XWC_DMTIMINGS
1864 m_tdelta0 = mx::sys::get_curr_time();
1867 if( m_deltaChannels.size() > 0 )
1873 derivedT::template log<software_critical>( { errno, rv,
"Error from makeDelta" } );
1879 #ifdef XWC_DMTIMINGS
1880 m_tdeltaf = mx::sys::get_curr_time();
1886 #ifdef XWC_DMTIMINGS
1887 m_tsat0 = mx::sys::get_curr_time();
1891 if( sem_post( &m_satSemaphore ) < 0 )
1893 derivedT::template log<software_critical>( { errno, 0,
"Error posting to semaphore" } );
1898 #ifdef XWC_DMTIMINGS
1900 m_tsatf = mx::sys::get_curr_time();
1903 if( m_piTimes.maxEntries() > 0 )
1905 m_piTimes.nextEntry( m_tf - m_t0 );
1906 m_satSem.nextEntry( m_tsatf - m_tsat0 );
1907 m_actProc.nextEntry( m_tact1 - m_tact0 );
1908 m_actCom.nextEntry( m_tact2 - m_tact1 );
1909 m_satUp.nextEntry( m_tact4 - m_tact3 );
1910 m_deltaUp.nextEntry( m_tdeltaf - m_tdelta0 );
1919template <
class derivedT,
typename realT>
1924 derivedT::template log<software_error>( { errno,
"DM is not ready to be initialized" } );
1932 if( ( rv = derived().initDM() ) < 0 )
1934 derivedT::template log<software_critical>( { errno, rv,
"Error from initDM" } );
1942template <
class derivedT,
typename realT>
1951 if( ( rv = derived().releaseDM() ) < 0 )
1953 derivedT::template log<software_critical>( { errno, rv,
"Error from releaseDM" } );
1958 if( ( rv = zeroAll(
true ) ) < 0 )
1960 derivedT::template log<software_error>( { errno, rv,
"Error from zeroAll" } );
1968template <
class derivedT,
typename realT>
1971 std::vector<std::string> tfs;
1973 bool gfn_logged =
false;
1975 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath,
"",
"",
".fits" );
1977 if( errc != mx::error_t::noerror )
1981 derivedT::template log<software_error>(
1982 { std::format(
"error getting flat files: {}", mx::errorMessage( errc ) ) } );
1991 for(
size_t n = 0; n < tfs.size(); ++n )
1993 if( mx::ioutils::pathStem( tfs[n] ) ==
"default" )
1995 tfs.erase( tfs.begin() + n );
2000 unsigned m_nFlatFiles = 5;
2003 if( tfs.size() >= m_nFlatFiles )
2005 std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
2007 for(
size_t n = 0; n < wtimes.size(); ++n )
2009 wtimes[n] = std::filesystem::last_write_time( tfs[n] );
2012 std::sort( wtimes.begin(), wtimes.end() );
2014 std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
2016 for(
size_t n = 0; n < tfs.size(); ++n )
2018 std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
2021 tfs.erase( tfs.begin() + n );
2027 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2032 bool changed =
false;
2033 for(
size_t n = 0; n < tfs.size(); ++n )
2036 m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2037 if( ir.second ==
true )
2040 ir.first->second = tfs[n];
2043 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2045 if(
it->second ==
"" )
2051 m_flatCommands.erase( itdel );
2058 if( derived().m_indiDriver )
2060 derived().m_indiDriver->sendDelProperty( m_indiP_flats );
2061 derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
2064 m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
2065 m_indiP_flats.setDevice( derived().configName() );
2066 m_indiP_flats.setName(
"flat" );
2067 m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
2068 m_indiP_flats.setState( pcf::IndiProperty::Idle );
2069 m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
2072 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2074 if(
it->first == m_flatCurrent || m_flatCurrent ==
"" )
2076 m_indiP_flats.add( pcf::IndiElement(
it->first, pcf::IndiElement::On ) );
2077 m_flatCurrent =
it->first;
2081 m_indiP_flats.add( pcf::IndiElement(
it->first, pcf::IndiElement::Off ) );
2085 if( m_flatDefault !=
"" )
2087 if( m_flatCurrent ==
"default" )
2089 m_indiP_flats.add( pcf::IndiElement(
"default", pcf::IndiElement::On ) );
2093 m_indiP_flats.add( pcf::IndiElement(
"default", pcf::IndiElement::Off ) );
2097 if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
2100 #ifndef DM_TEST_NOLOG
2101 derivedT::template log<software_error>( {
""} );
2108 if( derived().m_indiDriver )
2110 derived().m_indiDriver->sendDefProperty( m_indiP_flats );
2117template <
class derivedT,
typename realT>
2120 std::string
target = intarget;
2122 std::string targetPath;
2124 if(
target ==
"default" )
2127 targetPath = m_flatPath +
"/" + m_flatDefault +
".fits";
2133 targetPath = m_flatCommands.at(
target );
2137 derivedT::template log<text_log>(
"flat file " +
target +
" not found", logPrio::LOG_ERROR );
2142 m_flatLoaded =
false;
2145 mx::fits::fitsFile<realT> ff;
2147 mx::error_t errc = ff.read( m_flatCommand, targetPath );
2149 if( errc != mx::error_t::noerror )
2151 derivedT::template log<text_log>( std::format(
"error reading flat file {}: "
2154 mx::errorMessage( errc ),
2155 mx::errorName( errc ) ),
2156 logPrio::LOG_ERROR );
2160 if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
2162 derivedT::template log<text_log>( std::format(
"actuaor mask {}x{} is not same size as flag {}x{}",
2165 m_flatCommand.rows(),
2166 m_flatCommand.cols() ),
2167 logPrio::LOG_ERROR );
2172 m_flatCommand *= m_actMask();
2174 derivedT::template log<text_log>(
"loaded flat file " + targetPath );
2175 m_flatLoaded =
true;
2177 m_flatCurrent = intarget;
2179 if( m_indiP_flats.find(
"default" ) )
2181 if( m_flatCurrent ==
"default" )
2183 m_indiP_flats[
"default"] = pcf::IndiElement::On;
2187 m_indiP_flats[
"default"] = pcf::IndiElement::Off;
2191 for(
auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
2193 if( !m_indiP_flats.find( i->first ) )
2198 if( i->first == m_flatCurrent )
2200 m_indiP_flats[i->first] = pcf::IndiElement::On;
2204 m_indiP_flats[i->first] = pcf::IndiElement::Off;
2208 if( derived().m_indiDriver )
2210 derived().m_indiDriver->sendSetProperty( m_indiP_flats );
2221template <
class derivedT,
typename realT>
2224 if( m_shmimFlat ==
"" )
2231 derivedT::template log<text_log>(
"can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2235 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2237 derivedT::template log<text_log>(
"could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2241 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2243 ImageStreamIO_closeIm( &m_flatImageStream );
2244 derivedT::template log<text_log>(
"width mismatch between " + m_shmimFlat +
" and configured DM",
2245 logPrio::LOG_ERROR );
2249 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2251 ImageStreamIO_closeIm( &m_flatImageStream );
2252 derivedT::template log<text_log>(
"height mismatch between " + m_shmimFlat +
" and configured DM",
2253 logPrio::LOG_ERROR );
2259 bool flatSet = m_flatSet;
2262 if( loadFlat( m_flatCurrent ) < 0 )
2264 derivedT::template log<text_log>(
"error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
2266 m_flatSet = flatSet;
2271 ImageStreamIO_closeIm( &m_flatImageStream );
2272 derivedT::template log<text_log>(
"no flat loaded", logPrio::LOG_ERROR );
2276 if( m_flatCommand.rows() != m_dmWidth )
2278 ImageStreamIO_closeIm( &m_flatImageStream );
2279 derivedT::template log<text_log>(
"width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2283 if( m_flatCommand.cols() != m_dmHeight )
2285 ImageStreamIO_closeIm( &m_flatImageStream );
2286 derivedT::template log<text_log>(
"height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2290 m_flatImageStream.md->write = 1;
2295 memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight *
sizeof( realT ) );
2298 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2301 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2303 m_flatImageStream.md->cnt0++;
2304 m_flatImageStream.md->write = 0;
2307 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2311 ImageStreamIO_closeIm( &m_flatImageStream );
2317 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2319 derivedT::template log<text_log>(
"flat set" );
2325template <
class derivedT,
typename realT>
2328 if( m_shmimFlat ==
"" )
2335 derivedT::template log<text_log>(
"can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2339 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2341 derivedT::template log<text_log>(
"could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2345 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2347 ImageStreamIO_closeIm( &m_flatImageStream );
2348 derivedT::template log<text_log>(
"width mismatch between " + m_shmimFlat +
" and configured DM",
2349 logPrio::LOG_ERROR );
2353 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2355 ImageStreamIO_closeIm( &m_flatImageStream );
2356 derivedT::template log<text_log>(
"height mismatch between " + m_shmimFlat +
" and configured DM",
2357 logPrio::LOG_ERROR );
2361 m_flatImageStream.md->write = 1;
2366 memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2369 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2372 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2374 m_flatImageStream.md->cnt0++;
2375 m_flatImageStream.md->write = 0;
2376 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2381 ImageStreamIO_closeIm( &m_flatImageStream );
2383 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2385 derivedT::template log<text_log>(
"flat zeroed" );
2387 if( derived().zeroDM() < 0 )
2389 derivedT::template log<software_error>( {
"error from zeroDM" } );
2392 if( clearSat() < 0 )
2394 derivedT::template log<software_error>( {
"error from clearSat" } );
2401template <
class derivedT,
typename realT>
2404 std::vector<std::string> tfs;
2406 static bool gfn_logged =
false;
2408 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath,
"",
"",
".fits" );
2410 if( errc != mx::error_t::noerror )
2414 derivedT::template log<software_error>(
2415 { std::format(
"error getting test files: {}", mx::errorMessage( errc ) ) } );
2423 mx_error_check_rv( errc, -1 );
2425 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2430 bool changed =
false;
2431 for(
size_t n = 0; n < tfs.size(); ++n )
2434 m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2435 if( ir.second ==
true )
2438 ir.first->second = tfs[n];
2441 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2443 if(
it->second ==
"" )
2449 m_testCommands.erase( itdel );
2456 if( derived().m_indiDriver )
2458 derived().m_indiDriver->sendDelProperty( m_indiP_tests );
2459 derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
2462 m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
2463 m_indiP_tests.setDevice( derived().configName() );
2464 m_indiP_tests.setName(
"test" );
2465 m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
2466 m_indiP_tests.setState( pcf::IndiProperty::Idle );
2467 m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
2470 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2472 if(
it->first == m_testCurrent || m_testCurrent ==
"" )
2474 m_indiP_tests.add( pcf::IndiElement(
it->first, pcf::IndiElement::On ) );
2475 m_testCurrent =
it->first;
2479 m_indiP_tests.add( pcf::IndiElement(
it->first, pcf::IndiElement::Off ) );
2483 if( m_testDefault !=
"" )
2485 if( m_testCurrent ==
"default" )
2487 m_indiP_tests.add( pcf::IndiElement(
"default", pcf::IndiElement::On ) );
2491 m_indiP_tests.add( pcf::IndiElement(
"default", pcf::IndiElement::Off ) );
2495 if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
2497#ifndef DM_TEST_NOLOG
2498 derivedT::template log<software_error>( {
"" } );
2503 if( derived().m_indiDriver )
2505 derived().m_indiDriver->sendDefProperty( m_indiP_tests );
2512template <
class derivedT,
typename realT>
2515 std::string
target = intarget;
2517 if(
target ==
"default" )
2522 std::string targetPath;
2526 targetPath = m_testCommands.at(
target );
2530 derivedT::template log<text_log>(
"test file " +
target +
" not found", logPrio::LOG_ERROR );
2534 m_testLoaded =
false;
2536 mx::fits::fitsFile<realT> ff;
2537 mx::error_t errc = ff.read( m_testCommand, targetPath );
2538 if( errc != mx::error_t::noerror )
2540 derivedT::template log<text_log>( std::format(
"error reading test file {}: "
2543 mx::errorMessage( errc ),
2544 mx::errorName( errc ) ),
2545 logPrio::LOG_ERROR );
2549 derivedT::template log<text_log>(
"loaded test file " + targetPath );
2550 m_testLoaded =
true;
2552 m_testCurrent = intarget;
2554 if( m_indiP_tests.find(
"default" ) )
2556 if( m_testCurrent ==
"default" )
2558 m_indiP_tests[
"default"] = pcf::IndiElement::On;
2562 m_indiP_tests[
"default"] = pcf::IndiElement::Off;
2566 for(
auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
2568 if( !m_indiP_tests.find( i->first ) )
2573 if( i->first == m_testCurrent )
2575 m_indiP_tests[i->first] = pcf::IndiElement::On;
2579 m_indiP_tests[i->first] = pcf::IndiElement::Off;
2583 if( derived().m_indiDriver )
2584 derived().m_indiDriver->sendSetProperty( m_indiP_tests );
2592template <
class derivedT,
typename realT>
2596 if( m_shmimTest ==
"" )
2599 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2601 derivedT::template log<text_log>(
"could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2605 if( m_testImageStream.md->size[0] != m_dmWidth )
2607 ImageStreamIO_closeIm( &m_testImageStream );
2608 derivedT::template log<text_log>(
"width mismatch between " + m_shmimTest +
" and configured DM",
2609 logPrio::LOG_ERROR );
2613 if( m_testImageStream.md->size[1] != m_dmHeight )
2615 ImageStreamIO_closeIm( &m_testImageStream );
2616 derivedT::template log<text_log>(
"height mismatch between " + m_shmimTest +
" and configured DM",
2617 logPrio::LOG_ERROR );
2623 bool testSet = m_testSet;
2626 if( loadTest( m_testCurrent ) < 0 )
2628 derivedT::template log<text_log>(
"error loading test " + m_testCurrent, logPrio::LOG_ERROR );
2630 m_testSet = testSet;
2635 ImageStreamIO_closeIm( &m_testImageStream );
2636 derivedT::template log<text_log>(
"no test loaded", logPrio::LOG_ERROR );
2640 if( m_testCommand.rows() != m_dmWidth )
2642 ImageStreamIO_closeIm( &m_testImageStream );
2643 derivedT::template log<text_log>(
"width mismatch between test file and configured DM", logPrio::LOG_ERROR );
2647 if( m_testCommand.cols() != m_dmHeight )
2649 ImageStreamIO_closeIm( &m_testImageStream );
2650 derivedT::template log<text_log>(
"height mismatch between test file and configured DM", logPrio::LOG_ERROR );
2654 m_testImageStream.md->write = 1;
2659 memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight *
sizeof( realT ) );
2662 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2665 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2667 m_testImageStream.md->cnt0++;
2668 m_testImageStream.md->write = 0;
2669 ImageStreamIO_sempost( &m_testImageStream, -1 );
2674 ImageStreamIO_closeIm( &m_testImageStream );
2676 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2678 derivedT::template log<text_log>(
"test set" );
2683template <
class derivedT,
typename realT>
2686 if( m_shmimTest ==
"" )
2689 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2691 derivedT::template log<text_log>(
"could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2695 if( m_testImageStream.md[0].size[0] != m_dmWidth )
2697 ImageStreamIO_closeIm( &m_testImageStream );
2698 derivedT::template log<text_log>(
"width mismatch between " + m_shmimTest +
" and configured DM",
2699 logPrio::LOG_ERROR );
2703 if( m_testImageStream.md[0].size[1] != m_dmHeight )
2705 ImageStreamIO_closeIm( &m_testImageStream );
2706 derivedT::template log<text_log>(
"height mismatch between " + m_shmimTest +
" and configured DM",
2707 logPrio::LOG_ERROR );
2711 m_testImageStream.md->write = 1;
2716 memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2719 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2722 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2724 m_testImageStream.md->cnt0++;
2725 m_testImageStream.md->write = 0;
2728 ImageStreamIO_sempost( &m_testImageStream, -1 );
2732 ImageStreamIO_closeIm( &m_testImageStream );
2734 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2736 derivedT::template log<text_log>(
"test zeroed" );
2741template <
class derivedT,
typename realT>
2744 if( derived().m_shmimName ==
"" )
2751 for(
int n = 0; n < m_numChannels; ++n )
2754 snprintf( nstr,
sizeof( nstr ),
"%02d", n );
2755 std::string shmimN = derived().m_shmimName + nstr;
2757 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2759 derivedT::template log<text_log>(
"could not connect to channel " + shmimN, logPrio::LOG_WARNING );
2763 if( imageStream.md->size[0] != m_dmWidth )
2765 ImageStreamIO_closeIm( &imageStream );
2766 derivedT::template log<text_log>(
"width mismatch between " + shmimN +
" and configured DM",
2767 logPrio::LOG_ERROR );
2768 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2772 if( imageStream.md->size[1] != m_dmHeight )
2774 ImageStreamIO_closeIm( &imageStream );
2775 derivedT::template log<text_log>(
"height mismatch between " + shmimN +
" and configured DM",
2776 logPrio::LOG_ERROR );
2777 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2781 imageStream.md->write = 1;
2782 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2784 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2787 imageStream.md->atime = imageStream.md->writetime;
2789 imageStream.md->cnt0++;
2790 imageStream.md->write = 0;
2793 if( n == m_numChannels - 1 && !nosem )
2795 ImageStreamIO_sempost( &imageStream, -1 );
2798 ImageStreamIO_closeIm( &imageStream );
2801 derivedT::template log<text_log>(
"all channels zeroed", logPrio::LOG_NOTICE );
2803 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2807 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2815 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2818 if( ( rv = clearSat() ) < 0 )
2820 derivedT::template log<software_error>( { errno, rv,
"Error from clearSat" } );
2827template <
class derivedT,
typename realT>
2830 if( m_notDeltas.size() == 0 )
2835 m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
2837 for(
size_t n = 1; n < m_notDeltas.size(); ++n )
2839 m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
2842 m_outputDelta = m_outputShape() - m_totalFlat;
2844 m_totalDelta = ( *m_channels[m_deltas[0]] )();
2846 for(
size_t n = 1; n < m_deltas.size(); ++n )
2848 m_totalDelta += ( *m_channels[m_deltas[n]] )();
2851 m_outputDiff = m_totalDelta - m_outputDelta();
2856template <
class derivedT,
typename realT>
2859 if( m_shmimSat ==
"" || m_dmWidth == 0 || m_dmHeight == 0 )
2866 std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
2868 for(
size_t n = 0; n < sats.size(); ++n )
2870 std::string shmimN = sats[n];
2872 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2874 derivedT::template log<text_log>(
"could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
2878 if( imageStream.md->size[0] != m_dmWidth )
2880 ImageStreamIO_closeIm( &imageStream );
2881 derivedT::template log<text_log>(
"width mismatch between " + shmimN +
" and configured DM",
2882 logPrio::LOG_ERROR );
2883 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2887 if( imageStream.md->size[1] != m_dmHeight )
2889 ImageStreamIO_closeIm( &imageStream );
2890 derivedT::template log<text_log>(
"height mismatch between " + shmimN +
" and configured DM",
2891 logPrio::LOG_ERROR );
2892 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2896 imageStream.md->write = 1;
2897 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
2899 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2902 imageStream.md->atime = imageStream.md->writetime;
2904 imageStream.md->cnt0++;
2905 imageStream.md->write = 0;
2906 ImageStreamIO_sempost( &imageStream, -1 );
2908 ImageStreamIO_closeIm( &imageStream );
2911 m_accumSatMap.setZero();
2912 m_instSatMap.setZero();
2917template <
class derivedT,
typename realT>
2923template <
class derivedT,
typename realT>
2927 m_satThreadID = syscall( SYS_gettid );
2930 while( m_satThreadInit ==
true && derived().shutdown() == 0 )
2935 if( derived().shutdown() )
2940 uint32_t imsize[3] = { 0, 0, 0 };
2943 while( ( m_shmimSat ==
"" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
2948 if( derived().shutdown() )
2953 imsize[0] = m_dmWidth;
2954 imsize[1] = m_dmHeight;
2957 ImageStreamIO_createIm_gpu( &m_satImageStream,
2966 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2968 ImageStreamIO_createIm_gpu( &m_satPercImageStream,
2969 m_shmimSatPerc.c_str(),
2977 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2982 m_satImageStream.md->cnt1 = 0;
2983 m_satPercImageStream.md->cnt1 = 0;
2986 mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
2989 double t_accumst = mx::sys::get_curr_time();
2992 while( !derived().shutdown() )
2996 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
2998 derivedT::template log<software_critical>( { errno, 0,
"clock_gettime" } );
3004 if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
3007 for(
int rr = 0; rr < m_instSatMap.rows(); ++rr )
3009 for(
int cc = 0; cc < m_instSatMap.cols(); ++cc )
3011 m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
3017 if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
3024 for(
int rr = 0; rr < m_instSatMap.rows(); ++rr )
3026 for(
int cc = 0; cc < m_instSatMap.cols(); ++cc )
3028 m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
3029 if( m_satPercMap( rr, cc ) >= m_percThreshold )
3033 satmap( rr, cc ) = ( m_accumSatMap( rr, cc ) > 0 );
3039 if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
3041 ++m_intervalSatExceeds;
3045 m_intervalSatExceeds = 0;
3049 if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
3051 m_intervalSatTrip =
true;
3054 m_satImageStream.md->write = 1;
3055 m_satPercImageStream.md->write = 1;
3057 memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight *
sizeof( uint8_t ) );
3058 memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight *
sizeof(
float ) );
3061 clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
3062 m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
3065 m_satImageStream.md->atime = m_satImageStream.md->writetime;
3066 m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
3069 m_satImageStream.md->cnt1 = 0;
3070 m_satPercImageStream.md->cnt1 = 0;
3073 m_satImageStream.md->cnt0++;
3074 m_satPercImageStream.md->cnt0++;
3076 m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
3077 m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
3078 m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
3080 m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
3081 m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
3082 m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
3085 m_satImageStream.md->write = 0;
3086 ImageStreamIO_sempost( &m_satImageStream, -1 );
3088 m_satPercImageStream.md->write = 0;
3089 ImageStreamIO_sempost( &m_satPercImageStream, -1 );
3091 m_accumSatMap.setZero();
3093 t_accumst = mx::sys::get_curr_time( ts );
3098 if( errno == EINTR )
3106 if( errno != ETIMEDOUT )
3108 derivedT::template log<software_error>( { errno,
"sem_timedwait" } );
3116 ImageStreamIO_destroyIm( &m_satImageStream );
3118 ImageStreamIO_destroyIm( &m_satPercImageStream );
3122template <
class derivedT,
typename realT>
3125 if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
3127 for(
size_t n = 0; n < m_satTriggerDevice.size(); ++n )
3132 pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
3134 ipFreq.setDevice( m_satTriggerDevice[n] );
3135 ipFreq.setName( m_satTriggerProperty[n] );
3136 ipFreq.add( pcf::IndiElement(
"toggle" ) );
3137 ipFreq[
"toggle"] = pcf::IndiElement::Off;
3138 derived().sendNewProperty( ipFreq );
3140 derivedT::template log<text_log>(
"DM saturation threshold exceeded. Loop opened.",
3141 logPrio::LOG_WARNING );
3150template <
class derivedT,
typename realT>
3153 if( !derived().m_indiDriver )
3161template <
class derivedT,
typename realT>
3164 return static_cast<derivedT *
>( app )->newCallBack_init(
ipRecv );
3167template <
class derivedT,
typename realT>
3170 if(
ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
3172 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3175 if( !
ipRecv.find(
"request" ) )
3180 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3182 int rv = baseInitDM();
3185 return derivedT::template log<software_error, -1>( {
"error from initDM in INDI callback" } );
3192template <
class derivedT,
typename realT>
3195 return static_cast<derivedT *
>( app )->newCallBack_zero(
ipRecv );
3198template <
class derivedT,
typename realT>
3201 if(
ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey() )
3203 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3206 if( !
ipRecv.find(
"request" ) )
3209 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3211 return derived().zeroDM();
3216template <
class derivedT,
typename realT>
3219 return static_cast<derivedT *
>( app )->newCallBack_release(
ipRecv );
3222template <
class derivedT,
typename realT>
3225 if(
ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
3227 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3230 if( !
ipRecv.find(
"request" ) )
3233 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3235 return baseReleaseDM();
3240template <
class derivedT,
typename realT>
3243 return static_cast<derivedT *
>( app )->newCallBack_flats(
ipRecv );
3246template <
class derivedT,
typename realT>
3249 if(
ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
3251 derivedT::template log<software_error>( {
"invalid indi property received" } );
3255 std::string newFlat;
3257 if(
ipRecv.find(
"default" ) )
3259 if(
ipRecv[
"default"].getSwitchState() == pcf::IndiElement::On )
3261 newFlat =
"default";
3266 for(
auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
3268 if( !
ipRecv.find( i->first ) )
3271 if(
ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3275 derivedT::template log<text_log>(
"More than one flat selected", logPrio::LOG_ERROR );
3288 return loadFlat( newFlat );
3291template <
class derivedT,
typename realT>
3294 return static_cast<derivedT *
>( app )->newCallBack_setFlat(
ipRecv );
3297template <
class derivedT,
typename realT>
3300 if(
ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
3302 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3305 if( !
ipRecv.find(
"toggle" ) )
3308 if(
ipRecv[
"toggle"] == pcf::IndiElement::On )
3318template <
class derivedT,
typename realT>
3321 return static_cast<derivedT *
>( app )->newCallBack_tests(
ipRecv );
3324template <
class derivedT,
typename realT>
3327 if(
ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
3329 derivedT::template log<software_error>( {
"invalid indi property received" } );
3333 std::string newTest;
3335 if(
ipRecv.find(
"default" ) )
3337 if(
ipRecv[
"default"].getSwitchState() == pcf::IndiElement::On )
3339 newTest =
"default";
3344 for(
auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
3346 if( !
ipRecv.find( i->first ) )
3349 if(
ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3353 derivedT::template log<text_log>(
"More than one test selected", logPrio::LOG_ERROR );
3366 return loadTest( newTest );
3369template <
class derivedT,
typename realT>
3372 return static_cast<derivedT *
>( app )->newCallBack_setTest(
ipRecv );
3375template <
class derivedT,
typename realT>
3378 if(
ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
3380 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3383 if( !
ipRecv.find(
"toggle" ) )
3386 if(
ipRecv[
"toggle"] == pcf::IndiElement::On )
3396template <
class derivedT,
typename realT>
3399 return static_cast<derivedT *
>( app )->newCallBack_zeroAll(
ipRecv );
3402template <
class derivedT,
typename realT>
3405 if(
ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
3407 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3410 if( !
ipRecv.find(
"request" ) )
3413 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3416 m_indiP_zeroAll,
"request", pcf::IndiElement::On, derived().m_indiDriver,
INDI_BUSY );
3418 std::lock_guard<std::mutex> guard( derived().m_indiMutex );
3428#define DM_SETUP_CONFIG( cfig ) \
3429 if( dmT::setupConfig( cfig ) < 0 ) \
3431 log<software_error>( { "Error from dmT::setupConfig" } ); \
3432 m_shutdown = true; \
3440#define DM_LOAD_CONFIG( cfig ) \
3441 if( dmT::loadConfig( cfig ) < 0 ) \
3443 return log<software_error, -1>( { "Error from dmT::loadConfig" } ); \
3447#define DM_APP_STARTUP \
3448 if( dmT::appStartup() < 0 ) \
3450 return log<software_error, -1>( { "Error from dmT::appStartup" } ); \
3454#define DM_APP_LOGIC \
3455 if( dmT::appLogic() < 0 ) \
3457 return log<software_error, -1>( { "Error from dmT::appLogic" } ); \
3461#define DM_UPDATE_INDI \
3462 if( dmT::updateINDI() < 0 ) \
3464 return log<software_error, -1>( { "Error from dmT::updateINDI" } ); \
3468#define DM_APP_SHUTDOWN \
3469 if( dmT::appShutdown() < 0 ) \
3471 return log<software_error, -1>( { "Error from dmT::appShutdown" } ); \
#define IMAGESTRUCT_FLOAT
#define IMAGESTRUCT_UINT8
std::string m_calibPath
The path to this DM's calibration files.
std::map< std::string, std::string > m_flatCommands
Map of flat file name to full path.
int newCallBack_tests(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
bool m_testSet
Flag indicating whether the test command has been set.
std::string m_flatPath
The path to this DM's flat files (usually the same as calibPath)
float percThreshold() const
Get the saturation percentage threshold.
const std::string & testDefault() const
Get the.
const std::string & calibRelDir() const
uint32_t dmWidth() const
Get the DM Width.
std::string m_shmimDiff
The name of the shmim stream to write the difference to.
const mx::improc::eigenImage< uint16_t > & accumSatMap() const
pcf::IndiProperty m_indiP_setFlat
INDI toggle switch to set the current flat.
static void satThreadStart(dm *d)
Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
pcf::IndiProperty m_indiP_init
uint32_t m_dmWidth
The width of the images in the stream.
int satAvgInt() const
Get the saturation accumulation interval.
pcf::IndiProperty m_indiP_flats
INDI Selection switch containing the flat files.
int zeroFlat()
Zero the flat command on the DM.
static int st_newCallBack_init(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
int checkFlats()
Check the flats directory and update the list of flats if anything changes.
pcf::IndiProperty m_indiP_testShmim
Publish the shmim being used for the test command.
int checkTests()
Check the tests directory and update the list of tests if anything changes.
static int st_newCallBack_tests(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the test file.
const mx::improc::eigenImage< float > & satPercMap() const
std::string m_flatDefault
int setTest()
Send the current test command to the DM.
int clearSat()
Clear the saturation maps and zero the shared memory.
const std::vector< std::string > & satTriggerProperty() const
Get the saturation trigger property(ies)
static int st_newCallBack_zero(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
int loadTest(const std::string &target)
Load a test file.
int loadFlat(const std::string &target)
Load a flat file.
int m_satThreadPrio
Priority of the saturation thread. Usually ok to be 0.
IMAGE m_satPercImageStream
The ImageStreamIO shared memory buffer for the sat percentage map.
uint8_t dmDataType() const
Get the DM data type.
std::string m_calibRelDir
pcf::IndiProperty m_indiP_setTest
INDI toggle switch to set the current test pattern.
const std::string & shmimShape() const
Get the.
int findDMChannels()
Find the DM comb channels.
static constexpr uint8_t m_dmDataType
The ImageStreamIO type code.
std::vector< size_t > m_notDeltas
Indices of the channels which are not delta commands.
pcf::IndiProperty m_indiP_tests
INDI Selection switch containing the test pattern files.
mx::improc::eigenImage< realT > m_totalDelta
the total of all delta channels
mx::improc::eigenImage< realT > m_totalFlat
the total of all non-delta channels
std::string m_shmimDelta
The name of the shmim stream to write the desaturated delta command to.
std::string m_shmimFlat
The name of the shmim stream to write the flat to.
int processImage(void *curr_src, const dev::shmimT &sp)
const std::string & shmimSatPerc() const
Get the stream name for saturation percentage.
const std::string & shmimDelta() const
Get the.
std::vector< mx::improc::milkImage< realT > * > m_channels
uint32_t m_dmHeight
The height of the images in the stream.
int appShutdown()
DM shutdown.
std::string m_testPath
The path to this DM's test files (default is calibPath/tests;.
int newCallBack_zeroAll(const pcf::IndiProperty &ipRecv)
The callback for the zeroAll toggle switch, called by the static version.
bool m_flatSet
Flag indicating whether the flat command has been set.
pcf::IndiProperty m_satThreadProp
The property to hold the saturation thread details.
int baseReleaseDM()
Calls derived()->releaseDM() and then 0s all channels and the sat map.
std::string m_shmimSatPerc
The name of the shmim stream to write the saturation percentage map to.
int newCallBack_setTest(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
const std::string & calibPath() const
Get the.
static int st_newCallBack_flats(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the flat file.
std::string m_actMaskPath
The file name of the actuator mask for this DM.
mx::verbose::vvv verboseT
std::string m_testCurrent
bool m_flatLoaded
Flag indicating whether a flat is loaded in memory.
bool m_testLoaded
Flag indicating whether a test command is loaded in memory.
mx::improc::eigenImage< float > m_satPercMap
int setFlat(bool update=false)
Send the current flat command to the DM.
static int st_newCallBack_zeroAll(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for zeroing all channels.
const std::vector< std::string > & deltaChannels() const
int newCallBack_init(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
const std::string & shmimTest() const
Get the.
const std::vector< size_t > & notDeltas() const
const std::string & shmimFlat() const
Get the.
mx::improc::milkImage< realT > m_outputDelta
The true output delta command after saturation.
int zeroAll(bool nosem=false)
Zero all channels.
mx::improc::milkImage< realT > m_actMask
pid_t m_satThreadID
The ID of the saturation thread.
int intervalSatCountThreshold() const
Get the interval saturation count threshold.
int baseInitDM()
Calls derived()->initDM()
int zeroTest()
Zero the test command on the DM.
int m_satAvgInt
The time in milliseconds to accumulate saturation over.
int newCallBack_release(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
std::vector< std::string > m_satTriggerProperty
const std::string & flatDefault() const
Get the.
int updateINDI()
Update the INDI properties for this device controller.
std::vector< size_t > m_deltas
Indices of the channels which are delta commands.
mx::improc::milkImage< realT > m_outputDiff
The difference between command and true delta command after saturation.
int newCallBack_flats(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
sem_t m_satSemaphore
Semaphore used to tell the saturation thread to run.
pcf::IndiProperty m_indiP_flat
Property used to set and report the current flat.
IMAGE m_satImageStream
The ImageStreamIO shared memory buffer for the sat map.
int newCallBack_zero(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
bool m_satThreadInit
Synchronizer for thread startup, to allow priority setting to finish.
std::vector< std::string > m_satTriggerDevice
Device(s) with a toggle switch to toggle on saturation trigger.
mx::improc::eigenImage< realT > m_flatCommand
Data storage for the flat command.
int m_numChannels
The number of dmcomb channels found as part of allocation.
const mx::improc::eigenImage< uint8_t > & instSatMap() const
mx::improc::eigenImage< uint8_t > m_instSatMap
void satThreadExec()
Execute saturation processing.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
static int st_newCallBack_release(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
pcf::IndiProperty m_indiP_flatShmim
Publish the shmim being used for the flat.
float m_intervalSatThreshold
int whilePowerOff()
DM Poweroff Updates.
static int st_newCallBack_setFlat(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the flat.
static int st_newCallBack_setTest(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the test shape.
pcf::IndiProperty m_indiP_zeroAll
std::map< std::string, std::string > m_testCommands
Map of test file name to full path.
int makeDelta()
Calculate the delta command from the output shape.
mx::improc::milkImage< realT > m_outputShape
The true output shape after saturation.
std::vector< std::string > m_deltaChannels
The names of channels which are treated as delta commands.
const std::string & testPath() const
Get the.
IMAGE m_testImageStream
The ImageStreamIO shared memory buffer for the test.
std::string m_testDefault
std::string m_shmimTest
The name of the shmim stream to write the test to.
std::string m_shmimShape
The name of the shmim stream to write the desaturated true shape to.
int m_intervalSatCountThreshold
int appStartup()
Startup function.
int newCallBack_setFlat(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
mx::improc::eigenImage< uint16_t > m_accumSatMap
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int onPowerOff()
DM Poweroff.
float m_percThreshold
Threshold on percentage of frames an actuator is saturated over an interval.
int satThreadPrio() const
Get the saturation thread priority.
std::string m_shmimSat
The name of the shmim stream to write the saturation map to.
float intervalSatThreshold() const
Get the interval saturation threshold.
const mx::improc::eigenImage< float > & totalFlat() const
mx::improc::eigenImage< realT > m_testCommand
Data storage for the test command.
IMAGE m_flatImageStream
The ImageStreamIO shared memory buffer for the flat.
std::thread m_satThread
A separate thread for the actual saturation processing.
pcf::IndiProperty m_indiP_release
pcf::IndiProperty m_indiP_zero
uint32_t dmHeight() const
Get the DM Height.
int allocate(const dev::shmimT &sp)
Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
std::string m_flatCurrent
The name of the current flat command.
const std::vector< std::string > & satTriggerDevice() const
Get the saturation trigger device(s)
const std::string & shmimSat() const
Get the.
void intervalSatTrip()
Trigger loop openings because of excessive saturation.
const std::string & flatPath() const
Get the.
int appLogic()
DM application logic.
constexpr uint8_t ImageStreamTypeCode< float >()
constexpr uint8_t ImageStreamTypeCode()
constexpr uint8_t ImageStreamTypeCode< double >()
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
const pcf::IndiProperty & ipRecv
The MagAO-X generic shared memory monitor.
@ OPERATING
The device is operating, other than homing.
@ POWEROFF
The device power is off.
@ NOTHOMED
The device has not been homed.
@ HOMING
The device is homing.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.