Skip to content

Commit 5741bf5

Browse files
authored
fix(atik_efw): append reported fix proposals. (#1339)
* fix(atik-efw): handle EFW2 status responses Parse compact position/slot frames observed on hardware, skip FTDI status-only packets, and fall back safely when status remains unavailable. * refactor(atik-efw): address static analysis findings Avoid virtual dispatch from the destructor and simplify status frame parsing so bounds are explicit.
1 parent 8a17f78 commit 5741bf5

4 files changed

Lines changed: 239 additions & 77 deletions

File tree

indi-atik-efw/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ find_package(USB1 REQUIRED)
1010
find_package(Threads REQUIRED)
1111

1212
set(ATIK_EFW_VERSION_MAJOR 1)
13-
set(ATIK_EFW_VERSION_MINOR 0)
13+
set(ATIK_EFW_VERSION_MINOR 1)
1414

1515
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
1616
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/indi_atik_efw.xml.cmake ${CMAKE_CURRENT_BINARY_DIR}/indi_atik_efw.xml)

indi-atik-efw/atik_efw.cpp

Lines changed: 125 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "config.h"
2626

2727
#include <algorithm>
28+
#include <chrono>
2829
#include <deque>
2930
#include <iomanip>
3031
#include <memory>
@@ -53,9 +54,11 @@ constexpr uint16_t kFtdiFlowValue = 0x0303;
5354
constexpr unsigned int kControlTimeoutMs = 1000;
5455
constexpr unsigned int kReadTimeoutMs = 1000;
5556
constexpr unsigned int kWriteTimeoutMs = 1000;
57+
constexpr int kMaxStatusReadAttempts = 4;
5658
constexpr useconds_t kUsbResetDelayUs = 1000000;
5759
constexpr useconds_t kFtdiDelayUs = 200000;
5860
constexpr useconds_t kStatusDelayUs = 100000;
61+
constexpr std::chrono::milliseconds kMoveFallbackDelay {1500};
5962
constexpr int kDefaultSlots = 5;
6063
constexpr int kMaxSlots = 16;
6164
constexpr int kMaxResponseBytes = 64;
@@ -75,6 +78,9 @@ std::string bytesToHex(const std::vector<uint8_t> &data)
7578

7679
std::vector<uint8_t> sanitizeResponse(const std::vector<uint8_t> &raw)
7780
{
81+
if (raw.size() == 2)
82+
return {};
83+
7884
if (raw.size() >= 3 && raw[0] != kFrameByte && raw[2] == kFrameByte)
7985
return {raw.begin() + 2, raw.end()};
8086

@@ -90,37 +96,59 @@ int decodeByte(uint8_t value)
9096

9197
bool parseStatusResponse(const std::vector<uint8_t> &data, int *position, int *slots)
9298
{
93-
auto start = data.begin();
94-
while (start != data.end())
99+
std::vector<int> compactValues;
100+
size_t start = 0;
101+
while (start < data.size())
95102
{
96-
start = std::find(start, data.end(), kFrameByte);
97-
if (start == data.end())
98-
return false;
103+
while (start < data.size() && data[start] != kFrameByte)
104+
start++;
105+
if (start == data.size())
106+
break;
107+
108+
size_t end = start + 1;
109+
while (end < data.size() && data[end] != kFrameByte)
110+
end++;
111+
if (end == data.size())
112+
break;
99113

100-
auto end = std::find(start + 1, data.end(), kFrameByte);
101-
if (end == data.end())
102-
return false;
114+
size_t payloadSize = end - start - 1;
103115

104-
if (end - start >= 3 && *(start + 1) == kCmdStatus)
116+
// Some wheel firmware returns one frame containing the status command and values.
117+
if (payloadSize >= 2 && data[start + 1] == kCmdStatus)
105118
{
106-
int decodedPosition = decodeByte(*(start + 2));
119+
int decodedPosition = decodeByte(data[start + 2]);
107120
if (decodedPosition > 0 && position)
108121
*position = decodedPosition;
109122

110-
if (end - start >= 4)
123+
if (payloadSize >= 3)
111124
{
112-
int decodedSlots = decodeByte(*(start + 3));
125+
int decodedSlots = decodeByte(data[start + 3]);
113126
if (decodedSlots > 0 && slots)
114127
*slots = decodedSlots;
115128
}
116129

117130
return (decodedPosition > 0);
118131
}
119132

133+
// EFW2 hardware also returns two compact frames: #position##slot-count#.
134+
if (payloadSize == 1)
135+
{
136+
int value = decodeByte(data[start + 1]);
137+
if (value > 0 && value <= kMaxSlots)
138+
compactValues.push_back(value);
139+
}
140+
120141
start = end + 1;
121142
}
122143

123-
return false;
144+
if (compactValues.empty())
145+
return false;
146+
147+
if (position)
148+
*position = compactValues[0];
149+
if (slots && compactValues.size() > 1)
150+
*slots = compactValues[1];
151+
return true;
124152
}
125153

126154
bool writeCommand(AtikEfwUsb::DeviceHandle &handle, const std::vector<uint8_t> &command)
@@ -129,18 +157,55 @@ bool writeCommand(AtikEfwUsb::DeviceHandle &handle, const std::vector<uint8_t> &
129157
return (rc == static_cast<int>(command.size()));
130158
}
131159

132-
bool readResponse(AtikEfwUsb::DeviceHandle &handle, std::vector<uint8_t> *response)
160+
enum class StatusReadResult
133161
{
134-
if (!response)
135-
return false;
162+
NoResponse,
163+
Unparsed,
164+
Parsed
165+
};
136166

137-
unsigned char buffer[kMaxResponseBytes] = {0};
138-
int rc = handle.read(kEndpointIn, buffer, sizeof(buffer), kReadTimeoutMs);
139-
if (rc <= 0)
140-
return false;
167+
StatusReadResult readStatusResponse(AtikEfwUsb::DeviceHandle &handle, std::vector<uint8_t> *rawResponse,
168+
int *position, int *slots)
169+
{
170+
std::vector<uint8_t> raw;
171+
std::vector<uint8_t> cleaned;
172+
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(kReadTimeoutMs);
141173

142-
response->assign(buffer, buffer + rc);
143-
return true;
174+
for (int attempt = 0; attempt < kMaxStatusReadAttempts; attempt++)
175+
{
176+
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
177+
deadline - std::chrono::steady_clock::now()).count();
178+
if (remaining <= 0)
179+
break;
180+
181+
unsigned char buffer[kMaxResponseBytes] = {0};
182+
int rc = handle.read(kEndpointIn, buffer, sizeof(buffer), static_cast<unsigned int>(remaining));
183+
if (rc <= 0)
184+
break;
185+
186+
std::vector<uint8_t> packet(buffer, buffer + rc);
187+
raw.insert(raw.end(), packet.begin(), packet.end());
188+
189+
auto payload = sanitizeResponse(packet);
190+
cleaned.insert(cleaned.end(), payload.begin(), payload.end());
191+
192+
int parsedPosition = 0;
193+
int parsedSlots = 0;
194+
if (parseStatusResponse(cleaned, &parsedPosition, &parsedSlots))
195+
{
196+
if (rawResponse)
197+
*rawResponse = std::move(raw);
198+
if (position)
199+
*position = parsedPosition;
200+
if (slots)
201+
*slots = parsedSlots;
202+
return StatusReadResult::Parsed;
203+
}
204+
}
205+
206+
if (rawResponse)
207+
*rawResponse = raw;
208+
return raw.empty() ? StatusReadResult::NoResponse : StatusReadResult::Unparsed;
144209
}
145210

146211
bool initializeWheel(AtikEfwUsb::DeviceHandle &handle, std::string *error)
@@ -199,27 +264,18 @@ bool probeStatus(AtikEfwUsb::DeviceHandle &handle, bool requireParse, int *slotC
199264

200265
usleep(kStatusDelayUs);
201266

202-
std::vector<uint8_t> response;
203-
if (!readResponse(handle, &response))
204-
return false;
205-
206-
if (raw)
207-
*raw = response;
208-
209-
auto cleaned = sanitizeResponse(response);
210267
int slots = 0;
211268
int position = 0;
212-
bool parsed = parseStatusResponse(cleaned, &position, &slots);
213-
214-
if (parsed)
269+
auto result = readStatusResponse(handle, raw, &position, &slots);
270+
if (result == StatusReadResult::Parsed)
215271
{
216272
if (slotCount)
217273
*slotCount = slots;
218274
if (currentSlot)
219275
*currentSlot = position;
220276
}
221277

222-
return parsed || !requireParse;
278+
return result == StatusReadResult::Parsed || (!requireParse && result == StatusReadResult::Unparsed);
223279
}
224280

225281
std::string buildDeviceName(const AtikEfwUsb::DeviceInfo &info, size_t index, size_t count)
@@ -275,10 +331,7 @@ AtikEFW::AtikEFW(const DeviceDescriptor &desc, AtikEfwUsb::Backend &backend)
275331
setDeviceName(desc.name.c_str());
276332
}
277333

278-
AtikEFW::~AtikEFW()
279-
{
280-
Disconnect();
281-
}
334+
AtikEFW::~AtikEFW() = default;
282335

283336
const char *AtikEFW::getDefaultName()
284337
{
@@ -402,24 +455,6 @@ bool AtikEFW::sendCommand(const std::vector<uint8_t> &command)
402455
return true;
403456
}
404457

405-
bool AtikEFW::readResponse(std::vector<uint8_t> *response)
406-
{
407-
if (!handle_ || !response)
408-
return false;
409-
410-
unsigned char buffer[kMaxResponseBytes] = {0};
411-
int rc = handle_->read(kEndpointIn, buffer, sizeof(buffer), kReadTimeoutMs);
412-
if (rc <= 0)
413-
{
414-
LOGF_WARN("%s: no response (%d)", getDeviceName(), rc);
415-
return false;
416-
}
417-
418-
response->assign(buffer, buffer + rc);
419-
LOGF_DEBUG("%s: raw response %s", getDeviceName(), bytesToHex(*response).c_str());
420-
return true;
421-
}
422-
423458
bool AtikEFW::sendStatus(bool requireParse, int *slotCount, int *currentSlot)
424459
{
425460
const std::vector<uint8_t> statusCommand {kFrameByte, kCmdStatus, 0x00, kFrameByte};
@@ -430,17 +465,19 @@ bool AtikEFW::sendStatus(bool requireParse, int *slotCount, int *currentSlot)
430465
usleep(kStatusDelayUs);
431466

432467
std::vector<uint8_t> response;
433-
if (!readResponse(&response))
434-
return false;
435-
436-
auto cleaned = sanitizeResponse(response);
437468
int slots = 0;
438469
int position = 0;
439-
bool parsed = parseStatusResponse(cleaned, &position, &slots);
470+
auto result = readStatusResponse(*handle_, &response, &position, &slots);
471+
472+
if (!response.empty())
473+
LOGF_DEBUG("%s: raw response %s", getDeviceName(), bytesToHex(response).c_str());
440474

441-
if (!parsed)
475+
if (result != StatusReadResult::Parsed)
442476
{
443-
LOGF_WARN("%s: unparsed status response %s", getDeviceName(), bytesToHex(response).c_str());
477+
if (result == StatusReadResult::NoResponse)
478+
LOGF_WARN("%s: no status response", getDeviceName());
479+
else
480+
LOGF_WARN("%s: unparsed status response %s", getDeviceName(), bytesToHex(response).c_str());
444481
return !requireParse;
445482
}
446483

@@ -486,6 +523,8 @@ void AtikEFW::applySlotCount(int slots, bool updateProperty)
486523

487524
bool AtikEFW::Connect()
488525
{
526+
movementPending_ = false;
527+
489528
if (isSimulation())
490529
{
491530
int slots = slotCountHint_ > 0 ? slotCountHint_ : kDefaultSlots;
@@ -516,7 +555,7 @@ bool AtikEFW::Connect()
516555
int detectedPosition = 0;
517556
if (!sendStatus(false, &detectedSlots, &detectedPosition))
518557
{
519-
LOGF_ERROR("%s: no status response, aborting connection", getDeviceName());
558+
LOGF_ERROR("%s: failed to send status command", getDeviceName());
520559
handle_.reset();
521560
return false;
522561
}
@@ -530,6 +569,7 @@ bool AtikEFW::Connect()
530569
CurrentFilter = (detectedPosition > 0) ? detectedPosition : currentSlotHint_;
531570
if (CurrentFilter <= 0)
532571
CurrentFilter = 1;
572+
currentSlotHint_ = CurrentFilter;
533573

534574
applySlotCount(slots, true);
535575

@@ -546,6 +586,7 @@ bool AtikEFW::Connect()
546586

547587
bool AtikEFW::Disconnect()
548588
{
589+
movementPending_ = false;
549590
handle_.reset();
550591
return true;
551592
}
@@ -585,6 +626,8 @@ bool AtikEFW::SelectFilter(int targetFilter)
585626
return false;
586627
}
587628

629+
movementPending_ = true;
630+
movementStartedAt_ = std::chrono::steady_clock::now();
588631
return true;
589632
}
590633

@@ -601,6 +644,21 @@ int AtikEFW::QueryFilter()
601644
int position = 0;
602645
if (!sendStatus(true, &slots, &position))
603646
{
647+
if (movementPending_ && std::chrono::steady_clock::now() - movementStartedAt_ >= kMoveFallbackDelay)
648+
{
649+
LOGF_WARN("%s: status unavailable after filter change; assuming target slot %d",
650+
getDeviceName(), TargetFilter);
651+
CurrentFilter = TargetFilter;
652+
currentSlotHint_ = CurrentFilter;
653+
movementPending_ = false;
654+
FilterSlotNP[0].setValue(CurrentFilter);
655+
FilterSlotNP.apply();
656+
return CurrentFilter;
657+
}
658+
659+
if (movementPending_)
660+
return -1;
661+
604662
FilterSlotNP.setState(IPS_ALERT);
605663
FilterSlotNP.apply();
606664
return -1;
@@ -612,6 +670,9 @@ int AtikEFW::QueryFilter()
612670
if (position > 0)
613671
{
614672
CurrentFilter = position;
673+
currentSlotHint_ = CurrentFilter;
674+
if (CurrentFilter == TargetFilter)
675+
movementPending_ = false;
615676
FilterSlotNP[0].setValue(CurrentFilter);
616677
FilterSlotNP.apply();
617678
return CurrentFilter;

indi-atik-efw/atik_efw.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include <indifilterwheel.h>
2828

29+
#include <chrono>
2930
#include <memory>
3031
#include <string>
3132
#include <vector>
@@ -69,14 +70,15 @@ class AtikEFW : public INDI::FilterWheel
6970
bool configureDevice();
7071
bool sendStatus(bool requireParse, int *slotCount, int *currentSlot);
7172
bool sendCommand(const std::vector<uint8_t> &command);
72-
bool readResponse(std::vector<uint8_t> *response);
7373
void applySlotCount(int slots, bool updateProperty);
7474

7575
AtikEfwUsb::Backend &backend_;
7676
AtikEfwUsb::DeviceInfo deviceInfo_;
7777
std::unique_ptr<AtikEfwUsb::DeviceHandle> handle_;
7878
int slotCountHint_ {0};
7979
int currentSlotHint_ {0};
80+
bool movementPending_ {false};
81+
std::chrono::steady_clock::time_point movementStartedAt_;
8082

8183
INDI::PropertyNumber SlotCountNP {1};
8284
};

0 commit comments

Comments
 (0)