Instrument Neutral Distributed Interface INDI 2.2.5
indi_seestar_ccd.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Gord Tulloch. All rights reserved.
3
4 Seestar CCD INDI Driver
5
6 This driver inherits from the generic ASCOM Alpaca CCD driver and applies
7 workarounds for Seestar CCD's non-standard ImageBytes implementation.
8
9 This program is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by the Free
11 Software Foundation; either version 2 of the License, or (at your option)
12 any later version.
13
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23
24 The full GNU General Public License is included in this distribution in the
25 file called LICENSE.
26*******************************************************************************/
27
28#include "indi_seestar_ccd.h"
29#include "indicom.h"
30
31// We declare an auto pointer to SeestarCCD
32std::unique_ptr<SeestarCCD> seestar_ccd(new SeestarCCD());
33
45
50{
51 return "Seestar CCD";
52}
53
58{
59 // Call parent class first
61
62 // Set Seestar-specific default connection parameters
63 setDefaultServerAddress("SeeStar.local", "32323");
64
65 // Remove cooler capability (Seestar doesn't have active cooling)
66 uint32_t cap = GetCCDCapability();
67 cap &= ~CCD_HAS_COOLER;
69
70 return true;
71}
72
77{
78 // Call base class Connect
79 if (!AlpacaCCD::Connect())
80 return false;
81
82 // Force remove cooler capability after base class may have added it
83 // (Seestar reports cansetccdtemperature=true but we don't want to expose cooler controls)
84 uint32_t cap = GetCCDCapability();
85 cap &= ~CCD_HAS_COOLER;
87
88 return true;
89}
90
95{
96 // Call base class first
98
99 // Remove cooler properties if they were added by base class
100 if (isConnected())
101 {
102 deleteProperty("CCD_COOLER");
103 deleteProperty("CCD_TEMPERATURE");
104 deleteProperty("CCD_COOLER_POWER");
105 }
106
107 return true;
108}
109
114{
115 // Seestar doesn't have active cooling, so we don't allow temperature setting
117 return -1;
118}
119
124{
125 // Seestar requires method parameters FIRST, then ClientID/ClientTransactionID
126 // This is opposite of standard Alpaca ordering
127 std::string form_data;
128
129 // Add method-specific parameters first
130 for (auto& [key, value] : request.items())
131 {
132 if (!form_data.empty()) form_data += "&";
133
134 if (value.is_string())
135 {
136 form_data += key + "=" + value.get<std::string>();
137 }
138 else if (value.is_number_integer())
139 {
140 form_data += key + "=" + std::to_string(value.get<int>());
141 }
142 else if (value.is_number_float())
143 {
144 form_data += key + "=" + std::to_string(value.get<double>());
145 }
146 else if (value.is_boolean())
147 {
148 form_data += key + "=" + (value.get<bool>() ? "true" : "false");
149 }
150 }
151
152 // Add ClientID and ClientTransactionID AFTER method parameters
153 if (!form_data.empty()) form_data += "&";
154 form_data += "ClientID=" + std::to_string(getpid());
155 form_data += "&ClientTransactionID=" + std::to_string(getTransactionId());
156
157 return form_data;
158}
159
164{
165 // First, call the base class validation for basic checks
166 if (metadata->MetadataVersion != 1)
167 {
168 LOGF_ERROR("Unsupported ImageBytes metadata version: %d", metadata->MetadataVersion);
169 return false;
170 }
171
172 if (metadata->ErrorNumber != 0)
173 {
174 LOGF_ERROR("ImageBytes contains error: %d", metadata->ErrorNumber);
175 return false;
176 }
177
178 // Log the raw metadata before corrections
179 LOGF_INFO("Seestar raw metadata: Rank=%d, Dim1=%d, Dim2=%d, Dim3=%d, TransType=%d, DataStart=%d",
180 metadata->Rank, metadata->Dimension1, metadata->Dimension2, metadata->Dimension3,
181 metadata->TransmissionElementType, metadata->DataStart);
182
183 // Apply Seestar-specific workarounds
184 // Bug #1: Dimension1 and Dimension2 are swapped
185 // - Rank field is correct (2 for monochrome)
186 // - Dimension1 contains Height (1080) instead of Width
187 // - Dimension2 contains Width (1920) instead of Height
188 // - Dimension3 is correct (0 for 2D images)
189 int32_t actual_rank = metadata->Rank; // Rank is correct
190 int32_t actual_width = metadata->Dimension2; // Width is in Dimension2
191 int32_t actual_height = metadata->Dimension1; // Height is in Dimension1
192
193 // Validate the corrected values
194 if (actual_rank < 2 || actual_rank > 3)
195 {
196 LOGF_ERROR("Invalid corrected rank: %d (must be 2 or 3)", actual_rank);
197 return false;
198 }
199
200 if (actual_width <= 0 || actual_height <= 0)
201 {
202 LOGF_ERROR("Invalid corrected dimensions: %dx%d", actual_width, actual_height);
203 return false;
204 }
205
206 // Bug #2: TransmissionElementType handling
207 // Seestar reports type 8 (UInt16) which is correct for 2 bytes per pixel
208 // Keep it as-is (no workaround needed for this field)
209 int32_t actual_transmission_type = metadata->TransmissionElementType;
210
211 // Bug #3: DataStart is now correct (44)
212 // Earlier firmware versions reported 0, but current version reports 44 correctly
213 int32_t actual_data_start = metadata->DataStart;
214
215 // Verify data size matches expectations with corrected values
216 size_t bytes_per_element = 2; // Int16
217 uint32_t planes = (actual_rank == 3) ? 1 : 1; // For now assume mono
218 size_t expected_data_size = actual_width * actual_height * planes * bytes_per_element;
219 size_t actual_data_size = body_size - actual_data_start;
220
221 LOGF_DEBUG("Seestar ImageBytes corrected: %dx%dx%d, rank=%d, type=Int16, data_size=%zu (expected=%zu)",
222 actual_width, actual_height, planes, actual_rank, actual_data_size, expected_data_size);
223
224 // Allow some tolerance for the data size (within 1% or 1KB, whichever is larger)
225 size_t tolerance = std::max(expected_data_size / 100, (size_t)1024);
226 if (actual_data_size < expected_data_size - tolerance || actual_data_size > expected_data_size + tolerance)
227 {
228 LOGF_WARN("Data size mismatch: expected %zu bytes, got %zu bytes (tolerance: %zu)",
229 expected_data_size, actual_data_size, tolerance);
230 // Don't fail, just warn - we'll use what we got
231 }
232
233 // Update metadata structure with corrected values
234 metadata->Rank = actual_rank;
235 metadata->Dimension1 = actual_width;
236 metadata->Dimension2 = actual_height;
237 metadata->Dimension3 = (actual_rank == 3) ? planes : 0;
238 metadata->TransmissionElementType = actual_transmission_type;
239 metadata->DataStart = actual_data_start;
240
241 LOGF_INFO("Seestar ImageBytes: %dx%d, rank=%d, type=Int16, %zu bytes",
242 actual_width, actual_height, actual_rank, actual_data_size);
243
244 return true;
245}
double temperature
uint32_t getTransactionId()
void setDefaultServerAddress(const char *host, const char *port, bool force=false)
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
bool isConnected() const
uint32_t GetCCDCapability() const
GetCCDCapability returns the CCD capabilities.
Definition indiccd.h:161
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. All fields must be initialized.
Definition indiccd.cpp:140
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.
The SeestarCCD class provides Seestar specific workarounds for the ASCOM Alpaca CCD driver.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual std::string buildAlpacaFormData(const nlohmann::json &request) override
Build Alpaca form data with Seestar-specific parameter ordering.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual const char * getDefaultName() override
virtual int SetTemperature(double temperature) override
Set CCD temperature.
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
virtual bool parseImageBytesMetadata(ImageBytesMetadata *metadata, size_t body_size) override
Parse ImageBytes metadata with Seestar-specific workarounds.
SeestarCCD()
Main entry point - INDI handles this through the unique_ptr.
a class to store JSON values
iteration_proxy< iterator > items() noexcept
helper to access iterator member functions in range-based for
std::unique_ptr< SeestarCCD > seestar_ccd(new SeestarCCD())
Implementations for common driver routines.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80