42constexpr const char *kDefaultDeviceName =
"Gemini Power Box Hub Advanced v3";
43constexpr const char *kEnvironmentTab =
"Environment";
44constexpr uint32_t kHousekeepingPeriodMs = 1000;
45constexpr auto kTelemetryStaleTimeout = std::chrono::seconds(10);
46constexpr auto kReconnectInterval = std::chrono::seconds(5);
47constexpr auto kOperationConfirmationTimeout = std::chrono::seconds(8);
48constexpr int kMinimumSupportedFirmware = 308;
49constexpr const char *kSupportedIdentity =
"GeminiPowerBoxPlusAdv3";
50constexpr size_t kDcOutputCount = 4;
51constexpr size_t kDewOutputCount = 2;
52constexpr size_t kUsbOutputCount = 6;
53constexpr const char *kDcLabels[kDcOutputCount] = {
"DC 2",
"DC 3",
"DC 4",
"DC 5"};
54constexpr const char *kDewLabels[kDewOutputCount] = {
"DEW 6",
"DEW 7"};
55constexpr const char *kUsbLabels[kUsbOutputCount] = {
"USB A",
"USB B",
"USB C",
"USB D",
"USB E",
"USB F"};
57bool loggerSwitchEnabled(
ISwitch switches[],
unsigned int count,
const char *name)
59 for (
unsigned int i = 0; i < count; ++i)
61 if (std::strcmp(switches[i].name, name) == 0)
62 return switches[i].s ==
ISS_ON;
67bool indiLevelEnabled(
const char *debugLevelName,
const char *loggingLevelName)
75std::unique_ptr<GeminiPBH> driver(
new GeminiPBH());
85 registerDeviceCallbacks();
100 shutdownInProgress_.store(
true, std::memory_order_release);
101 userDisconnectRequested_ =
true;
103 removeDeviceCallbacks();
107 device_->disconnect();
110 pendingOperations_.clear();
111 callbackStaging_.
clear();
114void GeminiPBH::registerDeviceCallbacks()
119 telemetryCallbackHandle_ = device_->registerTelemetryCallback(
122 handleTelemetry(snapshot);
124 connectionCallbackHandle_ = device_->registerConnectionCallback(
127 handleConnectionEvent(event);
129 instrumentationListenerHandle_ = device_->registerInstrumentationListener(
132 handleInstrumentation(event);
136void GeminiPBH::removeDeviceCallbacks()
141 if (telemetryCallbackHandle_ != 0)
143 device_->removeTelemetryCallback(telemetryCallbackHandle_);
144 telemetryCallbackHandle_ = 0;
146 if (connectionCallbackHandle_ != 0)
148 device_->removeConnectionCallback(connectionCallbackHandle_);
149 connectionCallbackHandle_ = 0;
151 if (instrumentationListenerHandle_ != 0)
153 device_->removeInstrumentationListener(instrumentationListenerHandle_);
154 instrumentationListenerHandle_ = 0;
160 return kDefaultDeviceName;
167 if (!serialConnection_)
178 initializePowerInterfaceProperties();
179 initializeCustomProperties();
188void GeminiPBH::initializePowerInterfaceProperties()
192 for (
size_t i = 0; i < kDcOutputCount; ++i)
200 for (
size_t i = 0; i < kDewOutputCount; ++i)
209 for (
size_t i = 0; i < kUsbOutputCount; ++i)
218void GeminiPBH::initializeCustomProperties()
220 DeviceInfoTP[0].
fill(
"FIRMWARE_VERSION",
"Firmware Version",
"Unavailable");
223 EnvironmentNP[0].
fill(
"DEVICE_SURFACE_TEMPERATURE",
"Device Surface Temperature (C)",
"%.1f", -50, 100, 0, 0);
224 EnvironmentNP[1].
fill(
"AIR_TEMPERATURE",
"Air Temperature (C)",
"%.1f", -50, 100, 0, 0);
225 EnvironmentNP[2].
fill(
"HUMIDITY",
"Humidity (%)",
"%.1f", 0, 100, 0, 0);
226 EnvironmentNP[3].
fill(
"DEW_POINT",
"Calculated Dew Point (C)",
"%.1f", -50, 100, 0, 0);
231 property[0].fill(
"AUTO",
"Auto",
ISS_ON);
232 property[1].fill(
"MANUAL_PWM",
"Manual PWM",
ISS_OFF);
233 property[2].fill(
"SWITCH",
"Switch",
ISS_OFF);
234 const std::string name =
"DEW_" + std::to_string(index + 1) +
"_MODE";
235 const std::string label = std::string(kDewLabels[index]) +
" Mode";
238 fillModeProperty(Dew1ModeSP, 0);
239 fillModeProperty(Dew2ModeSP, 1);
241 DewOutputsNP[0].
fill(
"DEW_CHANNEL_1",
"DEW 6 Current Output (%)",
"%.0f", 0, 100, 0, 0);
242 DewOutputsNP[1].
fill(
"DEW_CHANNEL_2",
"DEW 7 Current Output (%)",
"%.0f", 0, 100, 0, 0);
249 defineConnectedProperties();
251 deleteConnectedProperties();
256void GeminiPBH::defineConnectedProperties()
258 if (connectedPropertiesDefined_)
272 connectedPropertiesDefined_ =
true;
275void GeminiPBH::deleteConnectedProperties()
277 if (!connectedPropertiesDefined_)
291 connectedPropertiesDefined_ =
false;
298 LOG_ERROR(
"Gemini Power Box Hub Advanced v3 semantic device is unavailable.");
302 userDisconnectRequested_ =
false;
303 driverState_ = DriverState::Connecting;
304 pendingOperations_.clear();
305 haveTelemetry_ =
false;
306 callbackStaging_.
clear();
308 const char *configuredPort = serialConnection_ ? serialConnection_->port() :
"";
309 const bool haveConfiguredPort = configuredPort !=
nullptr && *configuredPort !=
'\0';
310 const bool canAutoSearch = autoSearchEnabled();
311 if (!haveConfiguredPort && !canAutoSearch)
313 driverState_ = DriverState::Disconnected;
314 LOG_ERROR(
"No serial port configured for Gemini Power Box Hub Advanced v3.");
318 if (haveConfiguredPort)
320 LOGF_INFO(
"Connecting to Gemini Power Box Hub Advanced v3 on %s.", configuredPort);
324 return finishSuccessfulConnection(
false);
328 driverState_ = DriverState::Disconnected;
329 logDeviceError(
"connect", result.
error());
330 deleteConnectedProperties();
334 LOGF_WARN(
"Connection to %s failed; starting Gemini Power Box Hub Advanced v3 auto search.", configuredPort);
338 LOG_WARN(
"No serial port configured; starting Gemini Power Box Hub Advanced v3 auto search.");
344 driverState_ = DriverState::Disconnected;
345 LOG_ERROR(
"Gemini Power Box Hub Advanced v3 auto search was cancelled.");
346 deleteConnectedProperties();
350 const std::vector<geminipbh::ProbeResult> supportedDevices = supportedDiscoveryDevices(discovery);
351 if (supportedDevices.empty())
353 driverState_ = DriverState::Disconnected;
354 LOGF_ERROR(
"Gemini Power Box Hub Advanced v3 auto search found no supported device among %zu probe(s).", discovery.
probes.size());
355 deleteConnectedProperties();
358 if (supportedDevices.size() > 1)
360 driverState_ = DriverState::Disconnected;
361 for (
const auto &
device : supportedDevices)
362 LOGF_ERROR(
"Auto search candidate: %s firmware %s.",
363 device.preferredPort.c_str(),
device.firmware.raw.c_str());
364 LOG_ERROR(
"Multiple supported Gemini Power Box Hub Advanced v3 devices detected; select the desired serial port manually.");
365 deleteConnectedProperties();
372 driverState_ = DriverState::Disconnected;
374 deleteConnectedProperties();
378 LOGF_INFO(
"Auto search selected Gemini Power Box Hub Advanced v3 on %s (identity %s, firmware %s).",
382 if (!detectedConnect)
384 driverState_ = DriverState::Disconnected;
385 logDeviceError(
"connect after auto search", detectedConnect.
error());
386 deleteConnectedProperties();
390 return finishSuccessfulConnection(
true);
393bool GeminiPBH::finishSuccessfulConnection(
bool detectedPort)
395 driverState_ = DriverState::Connected;
396 defineConnectedProperties();
397 flushConnectionEvents();
398 flushTelemetryToINDI();
399 refreshPropertiesFromDevice();
404 detectedPortSaveRequestedForTesting_ =
true;
409 LOG_INFO(
"Gemini Power Box Hub Advanced v3 connected.");
413bool GeminiPBH::autoSearchEnabled()
const
416 if (autoSearchOverrideForTesting_)
418 return *autoSearchOverrideForTesting_;
423if (!property || property.
isEmpty())
427 return property.isSwitchOn(
"INDI_ENABLED");
430bool GeminiPBH::updateSerialPortSelection(
const std::string &port)
436 if (property && !property.
isEmpty() && property.
size() > 0)
438 property[0].setText(port.c_str());
439 property.setState(
IPS_OK);
444 if (!serialConnection_)
446 serialConnection_->setDefaultPort(port.c_str());
450bool GeminiPBH::discoveryCancellationRequested()
const
452 return shutdownInProgress_.load(std::memory_order_acquire) || userDisconnectRequested_;
458 if (discoveryFunctionForTesting_)
459 return discoveryFunctionForTesting_();
462 if (serialConnection_)
463 serialConnection_->Refresh(
true);
468 return discoveryCancellationRequested();
473std::vector<geminipbh::ProbeResult> GeminiPBH::supportedDiscoveryDevices(
476 std::vector<geminipbh::ProbeResult> supported;
477 for (
const auto &
device : discovery.devices)
480 if (isSupportedDiscoveryDevice(
device, reason))
482 supported.push_back(
device);
486 LOGF_WARN(
"Ignoring discovered Gemini device on %s: %s.",
487 device.preferredPort.c_str(), reason.c_str());
500 if (
device.identity != kSupportedIdentity)
502 reason =
"identity '" +
device.identity +
"' is not supported by this driver";
505 if (!
device.firmware.numeric)
507 reason =
"firmware version is unavailable";
510 if (*
device.firmware.numeric < kMinimumSupportedFirmware)
512 reason =
"firmware " + std::to_string(*
device.firmware.numeric) +
" is below the supported minimum " +
513 std::to_string(kMinimumSupportedFirmware);
521 userDisconnectRequested_ =
true;
522 driverState_ = DriverState::Disconnected;
524 haveTelemetry_ =
false;
528 result = device_->disconnect();
531 logDeviceError(
"disconnect", result.
error());
533 callbackStaging_.
clear();
535 deleteConnectedProperties();
536 LOG_INFO(
"Gemini Power Box Hub Advanced v3 disconnected.");
542 flushConnectionEvents();
543 flushTelemetryToINDI();
544 checkTelemetryFreshness();
545 checkPendingOperations();
546 attemptReconnectIfDue();
550void GeminiPBH::scheduleNextTimer()
552 if (!userDisconnectRequested_ && driverState_ != DriverState::Disconnected)
559 syncInstrumentationLevelWithIndiDebug();
564 if (dev !=
nullptr && std::strcmp(dev,
getDeviceName()) != 0)
568 return handlePowerSwitchChange(
PowerChannelsSP, states, names, n, gindi::PendingOperationKind::DcOutput);
570 return handlePowerSwitchChange(
USBPortSP, states, names, n, gindi::PendingOperationKind::UsbOutput);
572 return handlePowerSwitchChange(
DewChannelsSP, states, names, n, gindi::PendingOperationKind::HeaterEnabled);
574 return handleDewModeChange(0, Dew1ModeSP, states, names, n);
576 return handleDewModeChange(1, Dew2ModeSP, states, names, n);
578 const bool affectsInstrumentationLevel = name !=
nullptr &&
579 (std::strcmp(name,
"DEBUG") == 0 ||
580 std::strcmp(name,
"DEBUG_LEVEL") == 0 ||
581 std::strcmp(name,
"LOGGING_LEVEL") == 0);
583 if (handled && affectsInstrumentationLevel)
584 syncInstrumentationLevelWithIndiDebug();
590 if (dev !=
nullptr && std::strcmp(dev,
getDeviceName()) != 0)
594 return handleDewDutyCycleChange(values, names, n);
601 if (dev !=
nullptr && std::strcmp(dev,
getDeviceName()) != 0)
608 refreshLabelsFromTextProperties();
618 bool accepted =
true;
619 for (
int request = 0; request < n; ++request)
621 for (
size_t index = 0; index <
property.size(); ++index)
623 if (!property[index].isNameMatch(names[request]))
626 const bool enabled = states[request] ==
ISS_ON;
628 if (kind == gindi::PendingOperationKind::DcOutput)
629 rc = requestDcOutputChange(index, enabled);
630 else if (kind == gindi::PendingOperationKind::UsbOutput)
631 rc = requestUsbOutputChange(index, enabled);
632 else if (kind == gindi::PendingOperationKind::HeaterEnabled)
633 rc = requestHeaterEnabledChange(index, enabled);
648 char *names[],
int n)
651 bool foundTarget =
false;
652 for (
int request = 0; request < n; ++request)
654 if (states[request] !=
ISS_ON)
657 for (
size_t i = 0; i <
property.size(); ++i)
659 if (!property[i].isNameMatch(names[request]))
678 preservedManualPower_[heaterIndex] =
static_cast<unsigned>(std::round(
DewChannelDutyCycleNP[heaterIndex].getValue()));
680 if (!requestHeaterModeChange(heaterIndex, target))
692bool GeminiPBH::handleDewDutyCycleChange(
double values[],
char *names[],
int n)
694 bool accepted =
true;
695 for (
int request = 0; request < n; ++request)
702 if (!requestHeaterManualPowerChange(index, values[request]))
717 return requestDcOutputChange(port, enabled);
722 if (!requestHeaterEnabledChange(port, enabled))
724 return requestHeaterManualPowerChange(port, dutyCycle);
729 return requestUsbOutputChange(port, enabled);
732bool GeminiPBH::requestDcOutputChange(
size_t port,
bool enabled)
737 logDeviceError(
"set DC output", ready.
error());
744 logDeviceError(
"set DC output", result.
error());
751 logDeviceError(
"create DC confirmation target", target.error());
757 std::chrono::steady_clock::now() + kOperationConfirmationTimeout));
758 LOGF_DEBUG(
"DC output %zu accepted; waiting for telemetry confirmation.", port + 1);
762bool GeminiPBH::requestUsbOutputChange(
size_t port,
bool enabled)
767 logDeviceError(
"set USB output", ready.
error());
774 logDeviceError(
"set USB output", result.
error());
781 logDeviceError(
"create USB confirmation target", target.error());
787 std::chrono::steady_clock::now() + kOperationConfirmationTimeout));
788 LOGF_DEBUG(
"USB output %zu accepted; waiting for telemetry confirmation.", port + 1);
792bool GeminiPBH::requestHeaterEnabledChange(
size_t port,
bool enabled)
797 logDeviceError(
"set heater enabled state", ready.
error());
804 logDeviceError(
"set heater enabled state", result.
error());
811 logDeviceError(
"create heater-enabled confirmation target", target.error());
817 std::chrono::steady_clock::now() + kOperationConfirmationTimeout));
818 LOGF_DEBUG(
"Heater %zu enabled-state change accepted; waiting for telemetry confirmation.", port + 1);
827 logDeviceError(
"set heater mode", ready.
error());
834 logDeviceError(
"set heater mode", result.
error());
841 logDeviceError(
"create heater-mode confirmation target", target.error());
847 std::chrono::steady_clock::now() + kOperationConfirmationTimeout));
848 LOGF_DEBUG(
"Heater %zu mode change to %s accepted; waiting for telemetry confirmation.",
849 port + 1, heaterModeName(mode));
853bool GeminiPBH::requestHeaterManualPowerChange(
size_t port,
double percent)
858 logDeviceError(
"set heater manual power", ready.
error());
861 if (port >= kDewOutputCount || percent < 0.0 || percent > 100.0)
867 auto mode = device_->heaterMode(port);
870 logDeviceError(
"set heater manual power", mode.error());
875 LOGF_WARN(
"Heater %zu manual power is editable only in Manual PWM mode.", port + 1);
879 const unsigned rounded =
static_cast<unsigned>(std::round(percent));
883 logDeviceError(
"set heater manual power", result.
error());
887 preservedManualPower_[port] = rounded;
888 auto configured = device_->heaterManualPower(port);
890 LOGF_DEBUG(
"Heater %zu manual power setpoint accepted as driver/library configuration; telemetry does not independently confirm it.",
897 if (shutdownInProgress_.load(std::memory_order_acquire) || !device_)
900 switch (driverState_)
902 case DriverState::Connected:
904 case DriverState::CommunicationFailure:
906 case DriverState::Connecting:
907 case DriverState::Reconnecting:
909 case DriverState::Disconnected:
917 if (shutdownInProgress_.load(std::memory_order_acquire))
925 if (shutdownInProgress_.load(std::memory_order_acquire))
931void GeminiPBH::syncInstrumentationLevelWithIndiDebug()
938 instr::Level level = instr::Level::Off;
941 if (indiLevelEnabled(
"DBG_DEBUG",
"LOG_DEBUG"))
942 level = instr::Level::Verbose;
943 else if (indiLevelEnabled(
"DBG_SESSION",
"LOG_SESSION"))
944 level = instr::Level::Info;
945 else if (indiLevelEnabled(
"DBG_ERROR",
"LOG_ERROR"))
946 level = instr::Level::Error;
949 protocolInstrumentationForwarding_.store(level != instr::Level::Off, std::memory_order_release);
950 device_->setInstrumentationLevel(level);
955 if (shutdownInProgress_.load(std::memory_order_acquire))
957 if (!protocolInstrumentationForwarding_.load(std::memory_order_acquire))
966 LOGF_WARN(
"libgeminipbh: %s", message.c_str());
968 LOGF_ERROR(
"libgeminipbh: %s", message.c_str());
970 LOGF_INFO(
"libgeminipbh: %s", message.c_str());
972 LOGF_DEBUG(
"libgeminipbh: %s", message.c_str());
975void GeminiPBH::flushConnectionEvents()
980 processConnectionEvent(event);
988 if (userDisconnectRequested_ || shutdownInProgress_.load(std::memory_order_acquire))
990 driverState_ = DriverState::Connected;
991 defineConnectedProperties();
992 refreshPropertiesFromDevice();
993 LOG_INFO(
"Gemini Power Box Hub Advanced v3 semantic connection established.");
997 if (userDisconnectRequested_ || shutdownInProgress_.load(std::memory_order_acquire))
999 markCommunicationFailure(
"library reported communication failure");
1003 if (userDisconnectRequested_ || shutdownInProgress_.load(std::memory_order_acquire))
1005 driverState_ = DriverState::Connected;
1006 defineConnectedProperties();
1007 refreshPropertiesFromDevice();
1008 LOG_INFO(
"Gemini Power Box Hub Advanced v3 reconnect succeeded.");
1012 if (!userDisconnectRequested_ && !shutdownInProgress_.load(std::memory_order_acquire))
1013 markCommunicationFailure(
"library reported disconnect");
1018void GeminiPBH::flushTelemetryToINDI()
1022 if (!snapshot || driverState_ != DriverState::Connected)
1025 haveTelemetry_ =
true;
1026 lastTelemetryTime_ = snapshot->
timestamp;
1028 for (
size_t i = 0; i < kDcOutputCount; ++i)
1031 for (
size_t i = 0; i < kUsbOutputCount; ++i)
1034 for (
size_t i = 0; i < kDewOutputCount; ++i)
1042 for (
size_t mode = 0; mode < modeProperty.
size(); ++mode)
1043 modeProperty[mode].setState(mode == modeIndex ?
ISS_ON :
ISS_OFF);
1055 EnvironmentNP[3].setValue(snapshot->
dewPointC);
1061 DeviceInfoTP[0].setText(firmware.value().c_str());
1071 checkPendingOperations(&*snapshot);
1072 applyPendingAwareLiveStates();
1080 DewOutputsNP.
apply();
1082 EnvironmentNP.
apply();
1083 DeviceInfoTP.
apply();
1086void GeminiPBH::refreshPropertiesFromDevice()
1088 if (!device_ || !connectedPropertiesDefined_)
1091 auto firmware = device_->firmwareVersion();
1093 DeviceInfoTP[0].setText(firmware.value().c_str());
1096 for (
size_t i = 0; i < kDcOutputCount; ++i)
1098 auto value = device_->dcOutputState(i);
1101 for (
size_t i = 0; i < kUsbOutputCount; ++i)
1103 auto value = device_->usbOutputState(i);
1106 for (
size_t i = 0; i < kDewOutputCount; ++i)
1108 auto enabled = device_->heaterEnabled(i);
1111 auto mode = device_->heaterMode(i);
1116 for (
size_t j = 0; j < modeProperty.
size(); ++j)
1117 modeProperty[j].setState(j == modeIndex ?
ISS_ON :
ISS_OFF);
1120 auto manual = device_->heaterManualPower(i);
1122 preservedManualPower_[i] = manual.value();
1125 auto output = device_->heaterCurrentOutput(i);
1126 if (output) DewOutputsNP[i].setValue(output.value());
1129 auto voltage = device_->inputVoltage();
1130 auto current = device_->inputCurrent();
1131 auto power = device_->inputPower();
1137 auto surface = device_->deviceSurfaceTemperature();
1138 auto air = device_->airTemperature();
1139 auto humidity = device_->humidity();
1140 auto dewPoint = device_->dewPoint();
1141 if (surface) EnvironmentNP[0].setValue(surface.value());
1142 if (air) EnvironmentNP[1].setValue(air.value());
1154 applyPendingAwareLiveStates();
1156 DeviceInfoTP.
apply();
1158 EnvironmentNP.
apply();
1165 DewOutputsNP.
apply();
1168void GeminiPBH::checkTelemetryFreshness()
1170 checkTelemetryFreshness(std::chrono::steady_clock::now());
1173void GeminiPBH::checkTelemetryFreshness(std::chrono::steady_clock::time_point now)
1175 if (driverState_ != DriverState::Connected || !haveTelemetry_)
1178 if (now - lastTelemetryTime_ > kTelemetryStaleTimeout)
1179 markCommunicationFailure(
"telemetry is stale");
1182#ifdef GEMINI_TESTING
1185 userDisconnectRequested_ =
false;
1186 driverState_ = DriverState::Connected;
1187 haveTelemetry_ =
false;
1188 pendingOperations_.clear();
1189 callbackStaging_.
clear();
1190 defineConnectedProperties();
1192 flushTelemetryToINDI();
1195void GeminiPBH::checkTelemetryFreshnessForTesting(std::chrono::steady_clock::time_point now)
1197 checkTelemetryFreshness(now);
1200bool GeminiPBH::communicationFailureForTesting()
const
1202 return driverState_ == DriverState::CommunicationFailure;
1205IPState GeminiPBH::powerSensorsStateForTesting()
const
1210IPState GeminiPBH::environmentStateForTesting()
const
1215IPState GeminiPBH::powerChannelsStateForTesting()
const
1220IPState GeminiPBH::dewModeStateForTesting(
size_t index)
const
1229void GeminiPBH::simulateHeaterModePendingForTesting(
size_t heaterIndex,
geminipbh::HeaterMode targetMode,
1230 std::chrono::steady_clock::time_point deadline)
1233 if (!target)
return;
1242 checkPendingOperations(&snapshot);
1250void GeminiPBH::syncInstrumentationLevelForTesting()
1252 syncInstrumentationLevelWithIndiDebug();
1255void GeminiPBH::setDebugForTesting(
bool enable)
1260void GeminiPBH::setSerialPortForTesting(
const std::string &port)
1262 updateSerialPortSelection(port);
1265std::string GeminiPBH::serialPortForTesting()
const
1267 const char *port = serialConnection_ ? serialConnection_->port() :
"";
1268 return port ? port :
"";
1271void GeminiPBH::setAutoSearchForTesting(
bool enabled)
1273 autoSearchOverrideForTesting_ = enabled;
1278 discoveryFunctionForTesting_ = std::move(fn);
1281bool GeminiPBH::detectedPortSaveRequestedForTesting()
const
1283 return detectedPortSaveRequestedForTesting_;
1288void GeminiPBH::checkPendingOperations()
1292 checkPendingOperations(snapshot ? &*snapshot : nullptr);
1297 if (pendingOperations_.empty())
1300 const auto now = std::chrono::steady_clock::now();
1301 bool dcAlert =
false;
1302 bool usbAlert =
false;
1303 bool dewEnabledAlert =
false;
1304 std::array<bool, kDewOutputCount> dewModeAlert{};
1310 case gindi::PendingOperationKind::DcOutput:
1313 case gindi::PendingOperationKind::UsbOutput:
1316 case gindi::PendingOperationKind::HeaterEnabled:
1317 dewEnabledAlert =
true;
1319 case gindi::PendingOperationKind::HeaterMode:
1320 if (index < kDewOutputCount)
1321 dewModeAlert[index] =
true;
1326 pendingOperations_.erase(std::remove_if(pendingOperations_.begin(), pendingOperations_.end(),
1329 switch (gindi::evaluatePendingOperation(operation, snapshot, now))
1331 case gindi::PendingEvaluation::Confirmed:
1332 LOGF_DEBUG(
"Confirmed %s %zu from telemetry.",
1333 gindi::pendingOperationName(operation.kind), operation.index + 1);
1336 case gindi::PendingEvaluation::Pending:
1337 case gindi::PendingEvaluation::ValueUnavailable:
1340 case gindi::PendingEvaluation::Timeout:
1341 LOGF_WARN(
"Telemetry confirmation timed out for %s %zu.",
1342 gindi::pendingOperationName(operation.kind), operation.index + 1);
1343 markAlert(operation.kind, operation.index);
1346 case gindi::PendingEvaluation::NotConfirmable:
1347 LOGF_ERROR(
"Internal driver error: non-confirmable operation was added as pending for %s %zu.",
1348 gindi::pendingOperationName(operation.kind), operation.index + 1);
1349 markAlert(operation.kind, operation.index);
1352 case gindi::PendingEvaluation::InvalidTarget:
1353 LOGF_ERROR(
"Internal driver error: invalid pending target for %s %zu.",
1354 gindi::pendingOperationName(operation.kind), operation.index + 1);
1355 markAlert(operation.kind, operation.index);
1359 }), pendingOperations_.end());
1361 if (dcAlert) PowerChannelsSP.setState(
IPS_ALERT);
1362 if (usbAlert) USBPortSP.setState(
IPS_ALERT);
1363 if (dewEnabledAlert) DewChannelsSP.setState(
IPS_ALERT);
1364 if (dewModeAlert[0]) Dew1ModeSP.setState(
IPS_ALERT);
1365 if (dewModeAlert[1]) Dew2ModeSP.setState(
IPS_ALERT);
1367 applyPendingAwareLiveStates();
1368 PowerChannelsSP.apply();
1370 DewChannelsSP.apply();
1375void GeminiPBH::attemptReconnectIfDue()
1377 if (userDisconnectRequested_ || shutdownInProgress_.load(std::memory_order_acquire)
1378 || driverState_ != DriverState::CommunicationFailure || !device_)
1381 const auto now = std::chrono::steady_clock::now();
1382 if (lastReconnectAttempt_.time_since_epoch().count() != 0 && now - lastReconnectAttempt_ < kReconnectInterval)
1385 driverState_ = DriverState::Reconnecting;
1386 lastReconnectAttempt_ = now;
1389 LOG_INFO(
"Attempting Gemini Power Box Hub Advanced v3 reconnect through libgeminipbh.");
1394 driverState_ = DriverState::CommunicationFailure;
1395 logDeviceError(
"reconnect", result.
error());
1400 driverState_ = DriverState::Connected;
1401 defineConnectedProperties();
1402 flushConnectionEvents();
1403 flushTelemetryToINDI();
1404 refreshPropertiesFromDevice();
1407void GeminiPBH::markCommunicationFailure(
const std::string &reason)
1409 if (driverState_ == DriverState::CommunicationFailure || userDisconnectRequested_)
1412 driverState_ = DriverState::CommunicationFailure;
1413 haveTelemetry_ =
false;
1427 EnvironmentNP.
apply();
1434 DewOutputsNP.
apply();
1436 LOGF_WARN(
"Gemini Power Box Hub Advanced v3 communication failure: %s", reason.c_str());
1439void GeminiPBH::markHardwareProperties(
IPState state)
1441 if (!connectedPropertiesDefined_)
1455 DeviceInfoTP.
apply();
1457 EnvironmentNP.
apply();
1464 DewOutputsNP.
apply();
1467void GeminiPBH::failPendingOperations(
IPState state)
1469 if (pendingOperations_.empty())
1472 pendingOperations_.clear();
1473 if (!connectedPropertiesDefined_)
1496 return std::any_of(pendingOperations_.begin(), pendingOperations_.end(),
1499 return operation.kind == kind;
1505 return std::any_of(pendingOperations_.begin(), pendingOperations_.end(),
1508 return operation.kind == kind && operation.index == index;
1512void GeminiPBH::applyPendingAwareLiveStates()
1514 if (!connectedPropertiesDefined_ || driverState_ != DriverState::Connected)
1517 auto setState = [](
auto & property,
bool pending)
1525 setState(
PowerChannelsSP, hasPendingOperation(gindi::PendingOperationKind::DcOutput));
1526 setState(
USBPortSP, hasPendingOperation(gindi::PendingOperationKind::UsbOutput));
1527 setState(
DewChannelsSP, hasPendingOperation(gindi::PendingOperationKind::HeaterEnabled));
1528 setState(Dew1ModeSP, hasPendingOperation(gindi::PendingOperationKind::HeaterMode, 0));
1529 setState(Dew2ModeSP, hasPendingOperation(gindi::PendingOperationKind::HeaterMode, 1));
1534void GeminiPBH::logDeviceError(
const char *action,
geminipbh::Error error)
const
1539void GeminiPBH::refreshLabelsFromTextProperties()
1541 for (
size_t i = 0; i < kDcOutputCount; ++i)
1543 for (
size_t i = 0; i < kDewOutputCount; ++i)
1549 for (
size_t i = 0; i < kUsbOutputCount; ++i)
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
bool SetDewPort(size_t port, bool enabled, double dutyCycle) override
SetDewPort Set DEW/Dew port duty cycle.
bool SetPowerPort(size_t port, bool enabled) override
SetPowerPort Set power port on/off.
const char * getDefaultName() override
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool Disconnect() override
Disconnect from device.
void debugTriggered(bool enable) override
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool SetUSBPort(size_t port, bool enabled) override
SetUSBPort Set USB port on/off.
const char * getDeviceName() const
INDI::PropertySwitch getSwitch(const char *name) const
void registerProperty(const INDI::Property &property)
Register the property to be able to observe and update.
INDI::PropertyText getText(const char *name) const
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
void setActiveConnection(Connection::Interface *existingConnection)
setActiveConnection Switch the active connection to the passed connection plugin
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void setDebug(bool enable)
Toggle driver debug status A driver can be more verbose if Debug option is enabled by the client.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
virtual void debugTriggered(bool enable)
Inform driver that the debug option was triggered. This function is called after setDebug is triggere...
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
INDI::PropertySwitch DewChannelsSP
void SetCapability(uint32_t cap)
SetPowerCapability sets the Power capabilities. All capabilities must be initialized.
@ POWER_HAS_VOLTAGE_SENSOR
@ POWER_HAS_OVERALL_CURRENT
INDI::PropertyNumber PowerSensorsNP
void initProperties(const char *groupName, size_t nPowerPorts=0, size_t nDewPorts=0, size_t nVariablePorts=0, size_t nAutoDewPorts=0, size_t nUSBPorts=0)
Initialize Power properties. It is recommended to call this function within initProperties() of your ...
INDI::PropertyText PowerChannelLabelsTP
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process Power text properties.
INDI::PropertySwitch PowerChannelsSP
INDI::PropertySwitch USBPortSP
INDI::PropertyNumber DewChannelDutyCycleNP
INDI::PropertyText USBPortLabelsTP
INDI::PropertyText DewChannelLabelsTP
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
constexpr Error error() const
static constexpr Result failure(Error error)
static constexpr Result success()
static ValueResult failure(Error error)
std::optional< TelemetrySnapshot > takeTelemetry()
bool stageTelemetry(const TelemetrySnapshot &snapshot)
std::optional< TelemetrySnapshot > latestTelemetry() const
std::deque< ConnectionEvent > takeConnectionEvents()
bool stageConnectionEvent(ConnectionEvent event)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
Implementations for common driver routines.
#define LOGF_INFO(fmt,...)
#define LOGF_WARN(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
const char * DEVICE_AUTO_SEARCH
Toggle device auto search. If enabled and on connection failure with the default port,...
const char * DEVICE_PORT
Device serial (or bluetooth) connection port. The default value on Linux is /dev/ttyUSB0 while on Mac...
Namespace to encapsulate INDI client, drivers, and mediator classes.
const char * errorName(Error error)
ValueResult< OperationTarget > makeHeaterModeTarget(std::size_t index, HeaterMode mode)
bool heaterModeFromSwitchName(const char *name, HeaterMode &mode)
std::size_t heaterModeSwitchIndex(HeaterMode mode)
bool instrumentationDuplicatesSemanticDriverLog(const instrumentation::Event &event)
ValueResult< OperationTarget > makeBooleanTarget(PendingOperationKind kind, std::size_t index, bool enabled)
const char * heaterModeName(HeaterMode mode)
void replacePendingOperation(std::vector< PendingOperation > &operations, PendingOperation replacement)
DiscoveryResult discoverGeminiDevices(const DiscoveryOptions &options)
const char * probeStatusName(ProbeStatus status)
#define GEMINIPBH_VERSION_MAJOR
#define GEMINIPBH_VERSION_MINOR
static ISwitch DebugLevelS[nlevels]
static ISwitch LoggingLevelS[nlevels]
static unsigned int customLevel
std::function< bool()> shouldCancel
std::vector< ProbeResult > probes
std::string preferredPort
const double airTemperatureC
const std::array< HeaterMode, HeaterCount > heaterModes
const double deviceSurfaceTemperatureC
const std::array< bool, UsbOutputCount > usbOutputs
const double outputCurrentA
const std::array< double, HeaterCount > heaterOutputPercent
const double inputVoltageV
const std::array< bool, DcOutputCount > dcOutputs
const std::array< bool, HeaterCount > heaterEnabled
const std::chrono::steady_clock::time_point timestamp
const double humidityPercent
const double outputPowerW