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,37 +96,59 @@ int decodeByte(uint8_t value)
9096
9197bool 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
126154bool 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
146211bool 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
225281std::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
283336const 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-
423458bool 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
487524bool 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
547587bool 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;
0 commit comments