8#ifndef zaberLowLevelBinary_hpp
9#define zaberLowLevelBinary_hpp
14#include "../../libMagAOX/libMagAOX.hpp"
15#include "../../magaox_git_version.h"
22#define ZBC_CONNECTED ( 0 )
23#define ZBC_ERROR ( -1 )
24#define ZBC_NOT_CONNECTED ( 10 )
82 std::vector<zaberBinaryStage<zaberLowLevelBinary>>
m_stages;
121 const std::vector<std::string> &
serials
231 config.add(
"stages.renumberOnConnect",
233 "stages.renumberOnConnect",
239 "Whether to issue a broadcast renumber on connect. Default is true." );
241 config.add(
"stages.maxDiscoveryAddress",
243 "stages.maxDiscoveryAddress",
246 "maxDiscoveryAddress",
249 "Maximum device address to scan when matching configured serial numbers." );
251 config.add(
"stages.commandTimeout",
253 "stages.commandTimeout",
259 "Binary command timeout in milliseconds." );
261 config.add(
"stages.renumberPauseMs",
263 "stages.renumberPauseMs",
269 "Pause in milliseconds after a renumber command before discovery begins." );
297 if( config.isSetUnused( mx::app::iniFile::makeKey(
sections[
n],
"serial" ) ) )
305 config.configUnused(
tmp, mx::app::iniFile::makeKey(
sections[
n],
"serial" ) );
309 config.configUnused( targetSpeed, mx::app::iniFile::makeKey(
sections[
n],
"targetSpeed" ) );
319 int32_t &response, uint8_t deviceAddress, uint8_t commandNumber, int32_t data, uint8_t expectedReply )
339 if(
reply[0] != deviceAddress )
344 if(
reply[1] == 255 )
429 elevatedPrivileges
elPriv(
this );
478 std::vector<std::string>
serials;
542 const std::string serial =
serials[
n];
550 log<text_log>(
"stage @" + std::to_string(
address ) +
" with s/n " + serial +
" corresponds to " +
567 log<text_log>( std::format(
"stage {} with s/n {} not found in system.",
678 : pcf::IndiElement::Off );
915 m_stages[
i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off );
928 else if(
m_stages[
i].deviceStatus() ==
'I' )
953 elevatedPrivileges
ep(
this );
1025 log<text_log>(
"Recovering from binary stage communication error by resetting the connection.",
1063 (
m_stages[
i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
1096 for(
size_t n = 0; n < m_stages.size(); ++n )
1098 if(
ipRecv.find( m_stages[n].name() ) )
1100 long tgt =
ipRecv[m_stages[n].name()].get<
long>();
1103 if( m_stages[n].deviceAddress() < 1 )
1106 "stage {} with s/n {} not found in system.", m_stages[n].name(), m_stages[n].serial() ) );
1109 std::lock_guard<std::mutex> guard( m_indiMutex );
1110 if( m_stages[n].moveAbs( m_port, tgt ) < 0 )
1112 return log<
software_error, -1>( {
"error from moveAbs for " + m_stages[n].name() } );
1115 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), m_stages[n].tgtPos() );
1116 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1117 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string(
"OPERATING" ) );
1129 size_t stageno = std::numeric_limits<size_t>::max();
1132 for(
size_t n = 0; n < m_stages.size(); ++n )
1134 if(
ipRecv.find( m_stages[n].name() ) )
1138 return log<
software_error, -1>(
"more than one stage specified in req_home, rejecting request" );
1141 if( m_stages[n].deviceAddress() < 1 )
1144 std::format(
"stage {} with s/n {} not found", m_stages[n].name(), m_stages[n].serial() ) );
1152 if( !found || stageno >= m_stages.size() )
1154 return log<
software_error, -1>(
"no valid stage specified in req_home, rejecting request" );
1157 if(
ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1159 return log<software_warning, 0>(
1160 std::format(
"request off for stage {} in req_home", m_stages[stageno].name() ) );
1163 std::lock_guard<std::mutex> guard( m_indiMutex );
1164 if( m_stages[stageno].homing() )
1166 return log<software_warning, 0>(
1167 std::format(
"stage {} is already homing in req_home", m_stages[stageno].name() ) );
1170 if( m_stages[stageno].home( m_port ) < 0 )
1172 return log<
software_error, -1>( std::format(
"error from home for {}", m_stages[stageno].name() ) );
1176 updateIfChanged( m_indiP_parked, m_stages[stageno].name(), m_stages[stageno].parked() );
1177 updateIfChanged( m_indiP_curr_state, m_stages[stageno].name(), std::string(
"HOMING" ) );
1186 if( !
ipRecv.find(
"request" ) )
1191 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
1193 for(
size_t n = 0; n < m_stages.size(); ++n )
1195 if( m_stages[n].deviceAddress() < 1 )
1200 std::lock_guard<std::mutex> guard( m_indiMutex );
1201 if( m_stages[n].homing() )
1206 if( m_stages[n].home( m_port ) < 0 )
1208 return log<
software_error, -1>( {
"error from home for " + m_stages[n].name() } );
1212 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1213 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string(
"HOMING" ) );
1224 size_t stageno = std::numeric_limits<size_t>::max();
1227 for(
size_t n = 0; n < m_stages.size(); ++n )
1229 if(
ipRecv.find( m_stages[n].name() ) )
1233 return log<
software_error, -1>(
"more than one stage specified in req_halt, rejecting request" );
1236 if( m_stages[n].deviceAddress() < 1 )
1239 std::format(
"stage {} with s/n {} not present", m_stages[n].name(), m_stages[n].serial() ) );
1247 if( !found || stageno == std::numeric_limits<size_t>::max() )
1249 return log<
software_error, -1>(
"no valid stage specified in req_halt, rejecting request" );
1252 if(
ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1254 return log<software_warning, 0>(
1255 std::format(
"request off for stage {} in req_halt", m_stages[stageno].name() ) );
1258 std::lock_guard<std::mutex> guard( m_indiMutex );
1259 if( m_stages[stageno].stop( m_port ) < 0 )
1261 return log<
software_error, -1>( std::format(
"error from stop for {}", m_stages[stageno].name() ) );
1271 for(
size_t n = 0; n < m_stages.size(); ++n )
1273 if(
ipRecv.find( m_stages[n].name() ) &&
ipRecv[m_stages[n].name()].getSwitchState() == pcf::IndiElement::On )
1275 if( m_stages[n].deviceAddress() < 1 )
1277 log<software_error>(
1278 std::format(
"stage {} with s/n {} not present", m_stages[n].name(), m_stages[n].serial() ) );
1282 std::lock_guard<std::mutex> guard( m_indiMutex );
1283 if( m_stages[n].estop( m_port ) < 0 )
1285 log<software_error>( {
"error from estop for " + m_stages[n].name() } );
1298 size_t stageno = std::numeric_limits<size_t>::max();
1302 for(
size_t n = 0; n < m_stages.size(); ++n )
1304 if(
ipRecv.find( m_stages[n].name() ) )
1308 return log<
software_error, -1>(
"more than one stage specified in req_halt, rejecting request" );
1311 if( m_stages[n].deviceAddress() < 1 )
1313 return log<
software_error, -1>( std::format(
"stage {} with with "
1314 "s/n {} not present",
1316 m_stages[n].serial() ) );
1324 if( !found || stageno == std::numeric_limits<size_t>::max() )
1326 return log<
software_error, -1>(
"no valid stage specified in req_knob, rejecting request" );
1329 bool enable_knob =
ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
1331 std::lock_guard<std::mutex> guard( m_indiMutex );
1333 if( m_stages[stageno].enableKnob( m_port, enable_knob ) < 0 )
1335 return log<
software_error, -1>( std::format(
"error from enable knob for {}", m_stages[stageno].name() ) );
The base-class for XWCTk applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
std::string m_configName
The name of the configuration file (minus .conf).
stateCodes::stateCodeT state()
Get the current state code.
int powerState()
Returns the current power state.
int powerStateTarget()
Returns the target power state.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
std::string m_sysPath
The path to the system directory, for PID file, etc.
A class to manage the details of one binary-protocol stage in a Zaber system.
The low-level binary-protocol Zaber controller.
pcf::IndiProperty m_indiP_warn
Warning-state switch reported per configured stage.
bool m_renumberOnConnect
Whether to renumber the daisy chain during connect.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_ehalt)
virtual void setupConfig()
Set up application configuration.
int sendCommandNoReply(uint8_t deviceAddress, uint8_t commandNumber, int32_t data)
Send a broadcast or address-specific command with no awaited reply.
std::unordered_map< int, size_t > m_stageAddress
Map from binary device address to configured stage index.
int recoverFromError(bool devicePresent)
Recover from a binary-transport error without terminating the app.
int m_renumberPauseMs
Pause in milliseconds after renumbering before further commands are sent.
std::unordered_map< std::string, size_t > m_stageName
Map from configured stage name to configured stage index.
int connect()
Connect to the binary-protocol stage chain and discover configured devices.
virtual int onPowerOff()
Handle the transition into the powered-off state.
z_port m_port
Connected binary protocol port.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_halt)
virtual int appLogic()
Execute the main FSM for zaberLowLevelBinary.
virtual int whilePowerOff()
Execute the powered-off loop.
int m_commandTimeout
Binary command timeout in milliseconds.
int queryDevice(int32_t &response, uint8_t deviceAddress, uint8_t commandNumber, int32_t data, uint8_t expectedReply)
Query a device directly for discovery-time replies.
virtual int appShutdown()
Perform any shutdown tasks before exit.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_home_all)
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_knob_enable)
int loadStages()
Discover configured stages on the binary bus.
~zaberLowLevelBinary() noexcept
Destructor.
pcf::IndiProperty m_indiP_knob_enable
Enable or disable a stage's potentiometer.
int m_maxDiscoveryAddress
Maximum device address to probe while matching configured serial numbers.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_home)
pcf::IndiProperty m_indiP_curr_pos
Current raw position reported per configured stage.
pcf::IndiProperty m_indiP_parked
Parked-state bookkeeping reported per configured stage.
pcf::IndiProperty m_indiP_req_home_all
Global request to home all configured stages.
std::unordered_map< std::string, size_t > m_stageSerial
Map from configured serial number to configured stage index.
friend class zaberLowLevelBinary_test
pcf::IndiProperty m_indiP_curr_state
Current stage state reported per configured stage.
pcf::IndiProperty m_indiP_max_pos
Maximum raw position reported per configured stage.
bool m_stageDiscoveryInitialized
Whether the active connection has completed an initial discovery pass.
pcf::IndiProperty m_indiP_tgt_pos
Requested target raw position per configured stage.
std::vector< zaberBinaryStage< zaberLowLevelBinary > > m_stages
Stage helpers in configuration order.
int refreshStageDiscovery()
Refresh discovery on an already-connected binary bus.
pcf::IndiProperty m_indiP_req_home
Per-stage home requests.
pcf::IndiProperty m_indiP_req_halt
Per-stage halt requests.
pcf::IndiProperty m_indiP_req_ehalt
Per-stage emergency-halt requests.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_tgt_pos)
zaberLowLevelBinary()
Default constructor.
virtual void loadConfig()
Load application configuration.
pcf::IndiProperty m_indiP_lastHomed
Last-homed timestamps reported per configured stage.
virtual int appStartup()
Set up the INDI properties and restore retained stage state.
int m_numStages
Number of configured stages.
int resetConnection()
Reset the active binary connection bookkeeping.
pcf::IndiProperty m_indiP_temp
Driver temperature reported per configured stage.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
@ NODEVICE
No device exists for the application to control.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ 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.
@ CONNECTED
The application has connected to the device or service.
@ UNINITIALIZED
The application is unitialized, the default.
@ INITIALIZED
The application has been initialized, set just before calling appStartup().
@ NOTCONNECTED
The application is not connected to the device or service.
@ POWERON
The device power is on.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Provides a set of functions for interacting with Zaber devices in the binary protocol.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Software CRITICAL log entry.
A USB device as a TTY device.
std::string m_deviceName
The device path name, e.g. /dev/ttyUSB0.
int getDeviceName()
Get the device name from udev using the vendor, product, and serial number.
std::string m_idProduct
The product id 4-digit code.
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the USB section.
std::string m_serial
The serial number.
int loadConfig(mx::app::appConfigurator &config)
Load the USB section from an application configurator.
speed_t m_baudRate
The baud rate specification.
std::string m_idVendor
The vendor id 4-digit code.
#define TTY_E_DEVNOTFOUND
int zb_decode(int32_t *destination, const uint8_t *reply)
int zb_encode(uint8_t *destination, uint8_t device_number, uint8_t command_number, int32_t data)
int zb_disconnect(z_port port)
int zb_send(z_port port, const uint8_t *command)
int zb_receive(z_port port, uint8_t *destination)
int zb_drain(z_port port)
int zb_set_timeout(z_port port, int milliseconds)
int zb_connect(z_port *port, const char *port_name)
A class with details of a single binary-protocol Zaber stage.
MagAOX::app::MagAOXApp< true > MagAOXAppT
#define ZBC_NOT_CONNECTED