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;
5354constexpr unsigned int kControlTimeoutMs = 1000 ;
5455constexpr unsigned int kReadTimeoutMs = 1000 ;
5556constexpr unsigned int kWriteTimeoutMs = 1000 ;
57+ constexpr int kMaxStatusReadAttempts = 4 ;
5658constexpr useconds_t kUsbResetDelayUs = 1000000 ;
5759constexpr useconds_t kFtdiDelayUs = 200000 ;
5860constexpr useconds_t kStatusDelayUs = 100000 ;
61+ constexpr std::chrono::milliseconds kMoveFallbackDelay {1500 };
5962constexpr int kDefaultSlots = 5 ;
6063constexpr int kMaxSlots = 16 ;
6164constexpr int kMaxResponseBytes = 64 ;
@@ -75,6 +78,9 @@ std::string bytesToHex(const std::vector<uint8_t> &data)
7578
7679std::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,16 +96,17 @@ int decodeByte(uint8_t value)
9096
9197bool parseStatusResponse (const std::vector<uint8_t > &data, int *position, int *slots)
9298{
99+ // Some wheel firmware returns one frame containing the status command and values.
93100 auto start = data.begin ();
94101 while (start != data.end ())
95102 {
96103 start = std::find (start, data.end (), kFrameByte );
97104 if (start == data.end ())
98- return false ;
105+ break ;
99106
100107 auto end = std::find (start + 1 , data.end (), kFrameByte );
101108 if (end == data.end ())
102- return false ;
109+ break ;
103110
104111 if (end - start >= 3 && *(start + 1 ) == kCmdStatus )
105112 {
@@ -120,7 +127,37 @@ bool parseStatusResponse(const std::vector<uint8_t> &data, int *position, int *s
120127 start = end + 1 ;
121128 }
122129
123- return false ;
130+ // EFW2 hardware also returns two compact frames: #position##slot-count#.
131+ std::vector<int > values;
132+ start = data.begin ();
133+ while (start != data.end ())
134+ {
135+ start = std::find (start, data.end (), kFrameByte );
136+ if (start == data.end ())
137+ break ;
138+
139+ auto end = std::find (start + 1 , data.end (), kFrameByte );
140+ if (end == data.end ())
141+ break ;
142+
143+ if (end - start == 2 )
144+ {
145+ int value = decodeByte (*(start + 1 ));
146+ if (value > 0 && value <= kMaxSlots )
147+ values.push_back (value);
148+ }
149+
150+ start = end + 1 ;
151+ }
152+
153+ if (values.empty ())
154+ return false ;
155+
156+ if (position)
157+ *position = values[0 ];
158+ if (slots && values.size () > 1 )
159+ *slots = values[1 ];
160+ return true ;
124161}
125162
126163bool writeCommand (AtikEfwUsb::DeviceHandle &handle, const std::vector<uint8_t > &command)
@@ -129,18 +166,55 @@ bool writeCommand(AtikEfwUsb::DeviceHandle &handle, const std::vector<uint8_t> &
129166 return (rc == static_cast <int >(command.size ()));
130167}
131168
132- bool readResponse (AtikEfwUsb::DeviceHandle &handle, std::vector< uint8_t > *response)
169+ enum class StatusReadResult
133170{
134- if (!response)
135- return false ;
171+ NoResponse,
172+ Unparsed,
173+ Parsed
174+ };
136175
137- unsigned char buffer[kMaxResponseBytes ] = {0 };
138- int rc = handle.read (kEndpointIn , buffer, sizeof (buffer), kReadTimeoutMs );
139- if (rc <= 0 )
140- return false ;
176+ StatusReadResult readStatusResponse (AtikEfwUsb::DeviceHandle &handle, std::vector<uint8_t > *rawResponse,
177+ int *position, int *slots)
178+ {
179+ std::vector<uint8_t > raw;
180+ std::vector<uint8_t > cleaned;
181+ auto deadline = std::chrono::steady_clock::now () + std::chrono::milliseconds (kReadTimeoutMs );
141182
142- response->assign (buffer, buffer + rc);
143- return true ;
183+ for (int attempt = 0 ; attempt < kMaxStatusReadAttempts ; attempt++)
184+ {
185+ auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
186+ deadline - std::chrono::steady_clock::now ()).count ();
187+ if (remaining <= 0 )
188+ break ;
189+
190+ unsigned char buffer[kMaxResponseBytes ] = {0 };
191+ int rc = handle.read (kEndpointIn , buffer, sizeof (buffer), static_cast <unsigned int >(remaining));
192+ if (rc <= 0 )
193+ break ;
194+
195+ std::vector<uint8_t > packet (buffer, buffer + rc);
196+ raw.insert (raw.end (), packet.begin (), packet.end ());
197+
198+ auto payload = sanitizeResponse (packet);
199+ cleaned.insert (cleaned.end (), payload.begin (), payload.end ());
200+
201+ int parsedPosition = 0 ;
202+ int parsedSlots = 0 ;
203+ if (parseStatusResponse (cleaned, &parsedPosition, &parsedSlots))
204+ {
205+ if (rawResponse)
206+ *rawResponse = std::move (raw);
207+ if (position)
208+ *position = parsedPosition;
209+ if (slots)
210+ *slots = parsedSlots;
211+ return StatusReadResult::Parsed;
212+ }
213+ }
214+
215+ if (rawResponse)
216+ *rawResponse = raw;
217+ return raw.empty () ? StatusReadResult::NoResponse : StatusReadResult::Unparsed;
144218}
145219
146220bool initializeWheel (AtikEfwUsb::DeviceHandle &handle, std::string *error)
@@ -199,27 +273,18 @@ bool probeStatus(AtikEfwUsb::DeviceHandle &handle, bool requireParse, int *slotC
199273
200274 usleep (kStatusDelayUs );
201275
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);
210276 int slots = 0 ;
211277 int position = 0 ;
212- bool parsed = parseStatusResponse (cleaned, &position, &slots);
213-
214- if (parsed)
278+ auto result = readStatusResponse (handle, raw, &position, &slots);
279+ if (result == StatusReadResult::Parsed)
215280 {
216281 if (slotCount)
217282 *slotCount = slots;
218283 if (currentSlot)
219284 *currentSlot = position;
220285 }
221286
222- return parsed || !requireParse;
287+ return result == StatusReadResult::Parsed || ( !requireParse && result == StatusReadResult::Unparsed) ;
223288}
224289
225290std::string buildDeviceName (const AtikEfwUsb::DeviceInfo &info, size_t index, size_t count)
@@ -402,24 +467,6 @@ bool AtikEFW::sendCommand(const std::vector<uint8_t> &command)
402467 return true ;
403468}
404469
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-
423470bool AtikEFW::sendStatus (bool requireParse, int *slotCount, int *currentSlot)
424471{
425472 const std::vector<uint8_t > statusCommand {kFrameByte , kCmdStatus , 0x00 , kFrameByte };
@@ -430,17 +477,19 @@ bool AtikEFW::sendStatus(bool requireParse, int *slotCount, int *currentSlot)
430477 usleep (kStatusDelayUs );
431478
432479 std::vector<uint8_t > response;
433- if (!readResponse (&response))
434- return false ;
435-
436- auto cleaned = sanitizeResponse (response);
437480 int slots = 0 ;
438481 int position = 0 ;
439- bool parsed = parseStatusResponse (cleaned, &position, &slots);
482+ auto result = readStatusResponse (*handle_, &response, &position, &slots);
483+
484+ if (!response.empty ())
485+ LOGF_DEBUG (" %s: raw response %s" , getDeviceName (), bytesToHex (response).c_str ());
440486
441- if (!parsed )
487+ if (result != StatusReadResult::Parsed )
442488 {
443- LOGF_WARN (" %s: unparsed status response %s" , getDeviceName (), bytesToHex (response).c_str ());
489+ if (result == StatusReadResult::NoResponse)
490+ LOGF_WARN (" %s: no status response" , getDeviceName ());
491+ else
492+ LOGF_WARN (" %s: unparsed status response %s" , getDeviceName (), bytesToHex (response).c_str ());
444493 return !requireParse;
445494 }
446495
@@ -486,6 +535,8 @@ void AtikEFW::applySlotCount(int slots, bool updateProperty)
486535
487536bool AtikEFW::Connect ()
488537{
538+ movementPending_ = false ;
539+
489540 if (isSimulation ())
490541 {
491542 int slots = slotCountHint_ > 0 ? slotCountHint_ : kDefaultSlots ;
@@ -516,7 +567,7 @@ bool AtikEFW::Connect()
516567 int detectedPosition = 0 ;
517568 if (!sendStatus (false , &detectedSlots, &detectedPosition))
518569 {
519- LOGF_ERROR (" %s: no status response, aborting connection " , getDeviceName ());
570+ LOGF_ERROR (" %s: failed to send status command " , getDeviceName ());
520571 handle_.reset ();
521572 return false ;
522573 }
@@ -530,6 +581,7 @@ bool AtikEFW::Connect()
530581 CurrentFilter = (detectedPosition > 0 ) ? detectedPosition : currentSlotHint_;
531582 if (CurrentFilter <= 0 )
532583 CurrentFilter = 1 ;
584+ currentSlotHint_ = CurrentFilter;
533585
534586 applySlotCount (slots, true );
535587
@@ -546,6 +598,7 @@ bool AtikEFW::Connect()
546598
547599bool AtikEFW::Disconnect ()
548600{
601+ movementPending_ = false ;
549602 handle_.reset ();
550603 return true ;
551604}
@@ -585,6 +638,8 @@ bool AtikEFW::SelectFilter(int targetFilter)
585638 return false ;
586639 }
587640
641+ movementPending_ = true ;
642+ movementStartedAt_ = std::chrono::steady_clock::now ();
588643 return true ;
589644}
590645
@@ -601,6 +656,21 @@ int AtikEFW::QueryFilter()
601656 int position = 0 ;
602657 if (!sendStatus (true , &slots, &position))
603658 {
659+ if (movementPending_ && std::chrono::steady_clock::now () - movementStartedAt_ >= kMoveFallbackDelay )
660+ {
661+ LOGF_WARN (" %s: status unavailable after filter change; assuming target slot %d" ,
662+ getDeviceName (), TargetFilter);
663+ CurrentFilter = TargetFilter;
664+ currentSlotHint_ = CurrentFilter;
665+ movementPending_ = false ;
666+ FilterSlotNP[0 ].setValue (CurrentFilter);
667+ FilterSlotNP.apply ();
668+ return CurrentFilter;
669+ }
670+
671+ if (movementPending_)
672+ return -1 ;
673+
604674 FilterSlotNP.setState (IPS_ALERT );
605675 FilterSlotNP.apply ();
606676 return -1 ;
@@ -612,6 +682,9 @@ int AtikEFW::QueryFilter()
612682 if (position > 0 )
613683 {
614684 CurrentFilter = position;
685+ currentSlotHint_ = CurrentFilter;
686+ if (CurrentFilter == TargetFilter)
687+ movementPending_ = false ;
615688 FilterSlotNP[0 ].setValue (CurrentFilter);
616689 FilterSlotNP.apply ();
617690 return CurrentFilter;
0 commit comments