Instrument Neutral Distributed Interface INDI 2.2.5
device.cpp
Go to the documentation of this file.
1/*
2 Gemini Power Box Hub Advanced v3 INDI Driver
3
4 Copyright (C) 2026 Dieter R Kedrowitsch <[email protected]>
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU General Public License along with
17 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 The full GNU General Public License is included in this distribution in the
21 file called LICENSE.
22*/
23
25
26#include <array>
27#include <atomic>
28#include <mutex>
29#include <utility>
30
33
34namespace geminipbh
35{
36
37namespace
38{
39constexpr std::size_t kDcOutputCount = 4;
40constexpr std::size_t kUsbOutputCount = 6;
41constexpr std::size_t kHeaterCount = 2;
42constexpr unsigned kDcFirmwareBase = 2;
43constexpr unsigned kUsbFirmwareBase = 6;
44constexpr unsigned kHeaterFirmwareBase = 6;
45
46Result commandResult(int rc, bool connected, bool communicationFailed)
47{
48 switch (rc)
49 {
50 case 0:
51 return Result::success();
52 case 1:
54 case 2:
56 case 3:
57 return Result::failure(communicationFailed ? Error::CommunicationFailure :
59 default:
61 }
62}
63
64Result connectResult(int rc, bool cancellationObserved)
65{
66 if (cancellationObserved) return Result::failure(Error::Cancelled);
67
68 switch (rc)
69 {
70 case 0:
71 return Result::success();
72 case 6:
74 case 5:
75 case 7:
77 case 2:
78 case 3:
79 case 4:
80 default:
82 }
83}
84
85bool toProtocolMode(HeaterMode mode, DewMode &protocolMode)
86{
87 switch (mode)
88 {
90 protocolMode = DewMode::Automatic;
91 return true;
93 protocolMode = DewMode::Manual;
94 return true;
96 protocolMode = DewMode::Switch;
97 return true;
98 }
99 return false;
100}
101}
102
104{
106 {
108 {
109 std::function<bool()> callback;
110 {
111 std::lock_guard<std::mutex> lock(cancelMutex);
112 callback = cancelFunction;
113 }
114 const bool cancelled = callback && callback();
115 if (cancelled) cancellationObserved.store(true, std::memory_order_release);
116 return cancelled;
117 });
118
120 [this](const TelemetrySnapshot & snapshot)
121 {
122 std::lock_guard<std::mutex> lock(stateMutex);
123 latestTelemetry = std::make_shared<TelemetrySnapshot>(snapshot);
124 });
125
127 [this](ConnectionEvent event)
128 {
129 switch (event)
130 {
133 state.store(ConnectionState::Connected, std::memory_order_release);
134 break;
137 state.store(ConnectionState::CommunicationFailure, std::memory_order_release);
138 break;
141 state.store(ConnectionState::Disconnected, std::memory_order_release);
142 break;
143 }
144 });
145 }
146
153
170
172 {
173 std::lock_guard<std::mutex> lock(stateMutex);
174 latestTelemetry.reset();
175 }
176
177 std::shared_ptr<const TelemetrySnapshot> snapshot() const
178 {
179 std::lock_guard<std::mutex> lock(stateMutex);
180 return latestTelemetry;
181 }
182
183 Result finishCommand(int rc) const
184 {
185 ConnectionState current = state.load(std::memory_order_acquire);
186 return commandResult(rc,
189 }
190
192 mutable std::mutex stateMutex;
193 std::shared_ptr<const TelemetrySnapshot> latestTelemetry;
194 std::array<unsigned, kHeaterCount> heaterManualPower{};
195 std::array<bool, kHeaterCount> haveHeaterManualPower{{false, false}};
196 std::atomic<ConnectionState> state{ConnectionState::Disconnected};
197 mutable std::mutex lifecycleMutex;
198 mutable std::mutex cancelMutex;
199 std::function<bool()> cancelFunction;
200 std::atomic<bool> cancellationObserved{false};
203};
204
205Device::Device() : impl_(new Impl()) {}
206
207Device::~Device() = default;
208
209Result Device::connect(const std::string &port, int settleMs)
210{
211 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
212 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
213 if (impl_->state.load(std::memory_order_acquire) != ConnectionState::Disconnected)
215
216 impl_->invalidateTelemetry();
217 impl_->state.store(ConnectionState::Connecting, std::memory_order_release);
218 impl_->cancellationObserved.store(false, std::memory_order_release);
219 const int rc = impl_->protocol.connect(port, settleMs);
220 Result result = connectResult(rc, impl_->cancellationObserved.load(std::memory_order_acquire));
221 if (!result)
222 {
223 impl_->invalidateTelemetry();
224 impl_->state.store(ConnectionState::Disconnected, std::memory_order_release);
225 }
226 return result;
227}
228
230{
231 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
232 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
233
234 ConnectionState current = impl_->state.load(std::memory_order_acquire);
238
239 impl_->invalidateTelemetry();
240 impl_->state.store(ConnectionState::Reconnecting, std::memory_order_release);
241 impl_->cancellationObserved.store(false, std::memory_order_release);
242 const int rc = impl_->protocol.reconnect();
243 Result result = connectResult(rc, impl_->cancellationObserved.load(std::memory_order_acquire));
244 if (!result)
245 {
246 impl_->invalidateTelemetry();
247 impl_->state.store(ConnectionState::CommunicationFailure, std::memory_order_release);
248 }
249 return result;
250}
251
253{
254 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
255 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
256
257 ConnectionState current = impl_->state.load(std::memory_order_acquire);
261
262 impl_->invalidateTelemetry();
263 impl_->protocol.disconnect();
264 impl_->state.store(ConnectionState::Disconnected, std::memory_order_release);
265 return Result::success();
266}
267
269{
270 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
271 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
272
273 Result ready = impl_->requireConnected();
274 if (!ready) return ready;
275 return impl_->finishCommand(impl_->protocol.recoverStream());
276}
277
279{
280 return impl_->state.load(std::memory_order_acquire);
281}
282
284{
286}
287
292
294{
295 return impl_->protocol.registerTelemetryCallback(std::move(callback));
296}
297
299{
300 return impl_->protocol.removeTelemetryCallback(handle);
301}
302
304{
305 return impl_->protocol.registerConnectionCallback(std::move(callback));
306}
307
309{
310 return impl_->protocol.removeConnectionCallback(handle);
311}
312
314{
315 impl_->protocol.setInstrumentationLevel(level);
316}
317
319{
320 return impl_->protocol.instrumentationLevel();
321}
322
324{
325 return impl_->protocol.instrumentationEnabled(level);
326}
327
329{
330 return impl_->protocol.registerInstrumentationListener(std::move(listener));
331}
332
334{
335 return impl_->protocol.removeInstrumentationListener(handle);
336}
337
338void Device::setCancelFunction(std::function<bool()> callback)
339{
340 std::lock_guard<std::mutex> lock(impl_->cancelMutex);
341 impl_->cancelFunction = std::move(callback);
342}
343
344Result Device::setDcOutput(std::size_t channel, bool enabled)
345{
346 if (channel >= kDcOutputCount) return Result::failure(Error::InvalidChannel);
347 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
348 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
349 Result ready = impl_->requireConnected();
350 if (!ready) return ready;
351 return impl_->finishCommand(impl_->protocol.setOutput(static_cast<unsigned>(kDcFirmwareBase + channel), enabled));
352}
353
354ValueResult<bool> Device::dcOutputState(std::size_t channel) const
355{
356 if (channel >= kDcOutputCount) return ValueResult<bool>::failure(Error::InvalidChannel);
357 auto snapshot = impl_->snapshot();
359 return ValueResult<bool>(snapshot->dcOutputs[channel]);
360}
361
362Result Device::setUsbOutput(std::size_t channel, bool enabled)
363{
364 if (channel >= kUsbOutputCount) return Result::failure(Error::InvalidChannel);
365 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
366 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
367 Result ready = impl_->requireConnected();
368 if (!ready) return ready;
369 return impl_->finishCommand(impl_->protocol.setOutput(static_cast<unsigned>(kUsbFirmwareBase + channel), enabled));
370}
371
373{
374 if (channel >= kUsbOutputCount) return ValueResult<bool>::failure(Error::InvalidChannel);
375 auto snapshot = impl_->snapshot();
377 return ValueResult<bool>(snapshot->usbOutputs[channel]);
378}
379
380Result Device::setHeaterEnabled(std::size_t channel, bool enabled)
381{
382 if (channel >= kHeaterCount) return Result::failure(Error::InvalidChannel);
383 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
384 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
385 Result ready = impl_->requireConnected();
386 if (!ready) return ready;
387 return impl_->finishCommand(impl_->protocol.setDewEnabled(static_cast<unsigned>(kHeaterFirmwareBase + channel), enabled));
388}
389
390ValueResult<bool> Device::heaterEnabled(std::size_t channel) const
391{
392 if (channel >= kHeaterCount) return ValueResult<bool>::failure(Error::InvalidChannel);
393 auto snapshot = impl_->snapshot();
395 return ValueResult<bool>(snapshot->heaterEnabled[channel]);
396}
397
398Result Device::setHeaterMode(std::size_t channel, HeaterMode mode)
399{
400 if (channel >= kHeaterCount) return Result::failure(Error::InvalidChannel);
401 DewMode protocolMode = DewMode::Automatic;
402 if (!toProtocolMode(mode, protocolMode)) return Result::failure(Error::InvalidValue);
403 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
404 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
405 Result ready = impl_->requireConnected();
406 if (!ready) return ready;
407 return impl_->finishCommand(impl_->protocol.setDewMode(static_cast<unsigned>(kHeaterFirmwareBase + channel), protocolMode));
408}
409
411{
412 if (channel >= kHeaterCount) return ValueResult<HeaterMode>::failure(Error::InvalidChannel);
413 auto snapshot = impl_->snapshot();
415 return ValueResult<HeaterMode>(snapshot->heaterModes[channel]);
416}
417
418Result Device::setHeaterManualPower(std::size_t channel, unsigned percent)
419{
420 if (channel >= kHeaterCount) return Result::failure(Error::InvalidChannel);
421 if (percent > 100) return Result::failure(Error::InvalidValue);
422 std::unique_lock<std::mutex> lifecycleLock(impl_->lifecycleMutex, std::try_to_lock);
423 if (!lifecycleLock.owns_lock()) return Result::failure(Error::Busy);
424 Result ready = impl_->requireConnected();
425 if (!ready) return ready;
426
427 Result result = impl_->finishCommand(impl_->protocol.setDewManualPercent(static_cast<unsigned>
428 (kHeaterFirmwareBase + channel), percent));
429 if (result)
430 {
431 std::lock_guard<std::mutex> lock(impl_->stateMutex);
432 impl_->heaterManualPower[channel] = percent;
433 impl_->haveHeaterManualPower[channel] = true;
434 }
435 return result;
436}
437
439{
440 if (channel >= kHeaterCount) return ValueResult<unsigned>::failure(Error::InvalidChannel);
441 std::lock_guard<std::mutex> lock(impl_->stateMutex);
442 if (!impl_->haveHeaterManualPower[channel]) return ValueResult<unsigned>::failure(Error::ValueUnavailable);
443 return ValueResult<unsigned>(impl_->heaterManualPower[channel]);
444}
445
447{
448 if (channel >= kHeaterCount) return ValueResult<double>::failure(Error::InvalidChannel);
449 auto snapshot = impl_->snapshot();
451 return ValueResult<double>(snapshot->heaterOutputPercent[channel]);
452}
453
455{
456 auto snapshot = impl_->snapshot();
458 return ValueResult<double>(snapshot->deviceSurfaceTemperatureC);
459}
460
462{
463 auto snapshot = impl_->snapshot();
465 return ValueResult<double>(snapshot->airTemperatureC);
466}
467
469{
470 auto snapshot = impl_->snapshot();
472 return ValueResult<double>(snapshot->humidityPercent);
473}
474
476{
477 auto snapshot = impl_->snapshot();
479 return ValueResult<double>(snapshot->dewPointC);
480}
481
483{
484 auto snapshot = impl_->snapshot();
486 return ValueResult<double>(snapshot->inputVoltageV);
487}
488
490{
491 auto snapshot = impl_->snapshot();
493 return ValueResult<double>(snapshot->outputCurrentA);
494}
495
497{
498 auto snapshot = impl_->snapshot();
500 return ValueResult<double>(snapshot->outputPowerW);
501}
502
504{
505 std::string raw;
506 int numeric = 0;
507 if (!impl_->protocol.firmwareVersion(raw, numeric)) return ValueResult<std::string>::failure(Error::ValueUnavailable);
508 return ValueResult<std::string>(raw);
509}
510
511#ifdef GEMINI_TESTING
512void Device::injectStatusForTesting(const StatusFrame &status)
513{
514 impl_->protocol.injectStatusForTesting(status);
515}
516
517void Device::emitConnectionEventForTesting(ConnectionEvent event)
518{
519 impl_->protocol.emitConnectionEventForTesting(event);
520}
521
522void Device::setConnectedForTesting(bool connected)
523{
524 impl_->state.store(connected ? ConnectionState::Connected : ConnectionState::Disconnected, std::memory_order_release);
525 impl_->protocol.acceptingWrites_.store(connected, std::memory_order_release);
526}
527
528std::size_t Device::queuedCommandCountForTesting() const
529{
530 return impl_->protocol.queuedCommandCountForTesting();
531}
532#endif
533
534} // namespace geminipbh
instrumentation::Dispatcher::ListenerHandle InstrumentationListenerHandle
Definition device.h:59
void setInstrumentationLevel(instrumentation::Level level)
Definition device.cpp:313
bool removeTelemetryCallback(TelemetryCallbackHandle handle)
Definition device.cpp:298
TelemetryCallbackHandle registerTelemetryCallback(TelemetryCallback callback)
Definition device.cpp:293
std::function< void(ConnectionEvent)> ConnectionCallback
Definition device.h:56
ValueResult< bool > heaterEnabled(std::size_t channel) const
Definition device.cpp:390
ConnectionCallbackHandle registerConnectionCallback(ConnectionCallback callback)
Definition device.cpp:303
Result recoverTelemetryStream()
Definition device.cpp:268
ConnectionState connectionState() const
Definition device.cpp:278
bool removeInstrumentationListener(InstrumentationListenerHandle handle)
Definition device.cpp:333
ValueResult< bool > usbOutputState(std::size_t channel) const
Definition device.cpp:372
ValueResult< std::string > firmwareVersion() const
Definition device.cpp:503
std::uint64_t TelemetryCallbackHandle
Definition device.h:55
Result reconnect()
Definition device.cpp:229
bool hasCommunicationFailure() const
Definition device.cpp:288
std::uint64_t ConnectionCallbackHandle
Definition device.h:57
instrumentation::Level instrumentationLevel() const
Definition device.cpp:318
ValueResult< HeaterMode > heaterMode(std::size_t channel) const
Definition device.cpp:410
ValueResult< double > inputCurrent() const
Definition device.cpp:489
Result setHeaterEnabled(std::size_t channel, bool enabled)
Definition device.cpp:380
Result setHeaterManualPower(std::size_t channel, unsigned percent)
Definition device.cpp:418
InstrumentationListenerHandle registerInstrumentationListener(InstrumentationListener listener)
Definition device.cpp:328
Result disconnect()
Definition device.cpp:252
ValueResult< double > heaterCurrentOutput(std::size_t channel) const
Definition device.cpp:446
ValueResult< double > dewPoint() const
Definition device.cpp:475
ValueResult< unsigned > heaterManualPower(std::size_t channel) const
Definition device.cpp:438
ValueResult< double > airTemperature() const
Definition device.cpp:461
Result setHeaterMode(std::size_t channel, HeaterMode mode)
Definition device.cpp:398
instrumentation::Dispatcher::Listener InstrumentationListener
Definition device.h:58
ValueResult< double > deviceSurfaceTemperature() const
Definition device.cpp:454
ValueResult< double > inputVoltage() const
Definition device.cpp:482
void setCancelFunction(std::function< bool()> callback)
Definition device.cpp:338
ValueResult< double > humidity() const
Definition device.cpp:468
bool removeConnectionCallback(ConnectionCallbackHandle handle)
Definition device.cpp:308
Result setUsbOutput(std::size_t channel, bool enabled)
Definition device.cpp:362
Result connect(const std::string &port, int settleMs=0)
Definition device.cpp:209
ValueResult< double > inputPower() const
Definition device.cpp:496
std::function< void(const TelemetrySnapshot &)> TelemetryCallback
Definition device.h:54
bool isConnected() const
Definition device.cpp:283
ValueResult< bool > dcOutputState(std::size_t channel) const
Definition device.cpp:354
Result setDcOutput(std::size_t channel, bool enabled)
Definition device.cpp:344
bool instrumentationEnabled(instrumentation::Level level) const
Definition device.cpp:323
void setCancelFunction(std::function< bool()> fn)
Definition protocol.h:170
std::uint64_t TelemetryCallbackHandle
Definition protocol.h:51
TelemetryCallbackHandle registerTelemetryCallback(TelemetryCallback callback)
Definition protocol.cpp:885
bool removeTelemetryCallback(TelemetryCallbackHandle handle)
Definition protocol.cpp:896
std::uint64_t ConnectionCallbackHandle
Definition protocol.h:53
bool removeConnectionCallback(ConnectionCallbackHandle handle)
Definition protocol.cpp:930
ConnectionCallbackHandle registerConnectionCallback(ConnectionCallback callback)
Definition protocol.cpp:919
static constexpr Result failure(Error error)
Definition result.h:56
static constexpr Result success()
Definition result.h:52
static ValueResult failure(Error error)
Definition result.h:86
ConnectionState
Definition device.h:43
std::mutex lifecycleMutex
Definition device.cpp:197
std::array< unsigned, kHeaterCount > heaterManualPower
Definition device.cpp:194
std::shared_ptr< const TelemetrySnapshot > snapshot() const
Definition device.cpp:177
GeminiProtocol::ConnectionCallbackHandle internalConnectionCallback_
Definition device.cpp:202
GeminiProtocol::TelemetryCallbackHandle internalTelemetryCallback_
Definition device.cpp:201
GeminiProtocol protocol
Definition device.cpp:191
std::atomic< bool > cancellationObserved
Definition device.cpp:200
std::array< bool, kHeaterCount > haveHeaterManualPower
Definition device.cpp:195
Result finishCommand(int rc) const
Definition device.cpp:183
std::function< bool()> cancelFunction
Definition device.cpp:199
std::shared_ptr< const TelemetrySnapshot > latestTelemetry
Definition device.cpp:193
std::atomic< ConnectionState > state
Definition device.cpp:196
Result requireConnected() const
Definition device.cpp:154