1 // Copyright 2020 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "InputCommon/ControllerInterface/Wiimote/Wiimote.h"
6 
7 #include "Common/BitUtils.h"
8 #include "Common/Logging/Log.h"
9 #include "Common/MathUtil.h"
10 #include "Core/Config/SYSCONFSettings.h"
11 #include "Core/HW/WiimoteEmu/ExtensionPort.h"
12 #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
13 #include "InputCommon/ControllerEmu/ControllerEmu.h"
14 #include "InputCommon/ControllerInterface/ControllerInterface.h"
15 
16 namespace ciface::Wiimote
17 {
18 static constexpr char SOURCE_NAME[] = "Bluetooth";
19 
20 static constexpr size_t IR_SENSITIVITY_LEVEL_COUNT = 5;
21 
22 template <typename T>
23 class Button final : public Core::Device::Input
24 {
25 public:
Button(const T * value,std::common_type_t<T> mask,std::string name)26   Button(const T* value, std::common_type_t<T> mask, std::string name)
27       : m_value(*value), m_mask(mask), m_name(std::move(name))
28   {
29   }
30 
GetName() const31   std::string GetName() const override { return m_name; }
32 
GetState() const33   ControlState GetState() const override { return (m_value & m_mask) != 0; }
34 
35 private:
36   const T& m_value;
37   const T m_mask;
38   const std::string m_name;
39 };
40 
41 // GetState returns value divided by supplied "extent".
42 template <typename T, bool Detectable>
43 class GenericInput : public Core::Device::Input
44 {
45 public:
GenericInput(const T * value,std::string name,ControlState extent)46   GenericInput(const T* value, std::string name, ControlState extent)
47       : m_value(*value), m_name(std::move(name)), m_extent(extent)
48   {
49   }
50 
IsDetectable() const51   bool IsDetectable() const override { return Detectable; }
52 
GetName() const53   std::string GetName() const override { return m_name; }
54 
GetState() const55   ControlState GetState() const final override { return ControlState(m_value) / m_extent; }
56 
57 protected:
58   const T& m_value;
59   const std::string m_name;
60   const ControlState m_extent;
61 };
62 
63 template <typename T>
64 using AnalogInput = GenericInput<T, true>;
65 
66 template <typename T>
67 using UndetectableAnalogInput = GenericInput<T, false>;
68 
69 // GetName() is appended with '-' or '+' based on sign of "extent" value.
70 template <bool Detectable>
71 class SignedInput final : public GenericInput<float, Detectable>
72 {
73 public:
74   using GenericInput<float, Detectable>::GenericInput;
75 
GetName() const76   std::string GetName() const override { return this->m_name + (this->m_extent < 0 ? '-' : '+'); }
77 };
78 
79 using SignedAnalogInput = SignedInput<true>;
80 using UndetectableSignedAnalogInput = SignedInput<false>;
81 
82 class Motor final : public Core::Device::Output
83 {
84 public:
Motor(ControlState * value)85   Motor(ControlState* value) : m_value(*value) {}
86 
GetName() const87   std::string GetName() const override { return "Motor"; }
88 
SetState(ControlState state)89   void SetState(ControlState state) override { m_value = state; }
90 
91 private:
92   ControlState& m_value;
93 };
94 
95 template <typename T>
QueueReport(T && report,std::function<void (ErrorCode)> ack_callback)96 void Device::QueueReport(T&& report, std::function<void(ErrorCode)> ack_callback)
97 {
98   // Maintain proper rumble state.
99   report.rumble = m_rumble;
100 
101   m_wiimote->QueueReport(report.REPORT_ID, &report, sizeof(report));
102 
103   if (ack_callback)
104     AddReportHandler(MakeAckHandler(report.REPORT_ID, std::move(ack_callback)));
105 }
106 
AddDevice(std::unique_ptr<WiimoteReal::Wiimote> wiimote)107 void AddDevice(std::unique_ptr<WiimoteReal::Wiimote> wiimote)
108 {
109   // Our real wiimote class requires an index.
110   // Within the pool it's only going to be used for logging purposes.
111   static constexpr int CIFACE_WIIMOTE_INDEX = 55;
112 
113   if (!wiimote->Connect(CIFACE_WIIMOTE_INDEX))
114   {
115     WARN_LOG(WIIMOTE, "WiiRemote: Failed to connect.");
116     return;
117   }
118 
119   wiimote->Prepare();
120   wiimote->EventLinked();
121 
122   g_controller_interface.AddDevice(std::make_shared<Device>(std::move(wiimote)));
123 }
124 
ReleaseDevices(std::optional<u32> count)125 void ReleaseDevices(std::optional<u32> count)
126 {
127   u32 removed_devices = 0;
128 
129   // Remove up to "count" remotes (or all of them if nullopt).
130   // Real wiimotes will be added to the pool.
131   g_controller_interface.RemoveDevice([&](const Core::Device* device) {
132     if (device->GetSource() != SOURCE_NAME || count == removed_devices)
133       return false;
134 
135     ++removed_devices;
136     return true;
137   });
138 }
139 
Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote)140 Device::Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote) : m_wiimote(std::move(wiimote))
141 {
142   using EmuWiimote = WiimoteEmu::Wiimote;
143 
144   // Buttons.
145   static constexpr std::pair<u16, const char*> button_masks[] = {
146       {EmuWiimote::BUTTON_A, "A"},       {EmuWiimote::BUTTON_B, "B"},
147       {EmuWiimote::BUTTON_ONE, "1"},     {EmuWiimote::BUTTON_TWO, "2"},
148       {EmuWiimote::BUTTON_MINUS, "-"},   {EmuWiimote::BUTTON_PLUS, "+"},
149       {EmuWiimote::BUTTON_HOME, "HOME"},
150   };
151 
152   for (auto& button : button_masks)
153     AddInput(new Button<u16>(&m_core_data.hex, button.first, button.second));
154 
155   static constexpr u16 dpad_masks[] = {
156       EmuWiimote::PAD_UP,
157       EmuWiimote::PAD_DOWN,
158       EmuWiimote::PAD_LEFT,
159       EmuWiimote::PAD_RIGHT,
160   };
161 
162   // Friendly orientation inputs.
163   static constexpr const char* const rotation_names[] = {"Pitch", "Roll", "Yaw"};
164   for (std::size_t i = 0; i != std::size(rotation_names); ++i)
165   {
166     AddInput(
167         new UndetectableSignedAnalogInput(&m_rotation_inputs.data[i], rotation_names[i], -1.f));
168     AddInput(new UndetectableSignedAnalogInput(&m_rotation_inputs.data[i], rotation_names[i], 1.f));
169   }
170 
171   // Raw accelerometer.
172   for (std::size_t i = 0; i != std::size(dpad_masks); ++i)
173     AddInput(new Button<u16>(&m_core_data.hex, dpad_masks[i], named_directions[i]));
174 
175   static constexpr std::array<std::array<const char*, 2>, 3> accel_names = {{
176       {"Accel Left", "Accel Right"},
177       {"Accel Backward", "Accel Forward"},
178       {"Accel Up", "Accel Down"},
179   }};
180 
181   for (std::size_t i = 0; i != m_accel_data.data.size(); ++i)
182   {
183     AddInput(new UndetectableAnalogInput<float>(&m_accel_data.data[i], accel_names[i][0], 1));
184     AddInput(new UndetectableAnalogInput<float>(&m_accel_data.data[i], accel_names[i][1], -1));
185   }
186 
187   // IR data.
188   static constexpr const char* const ir_names[] = {"IR Center X", "IR Center Y"};
189   for (std::size_t i = 0; i != std::size(ir_names); ++i)
190   {
191     AddInput(
192         new UndetectableSignedAnalogInput(&m_ir_state.center_position.data[i], ir_names[i], -1.f));
193     AddInput(
194         new UndetectableSignedAnalogInput(&m_ir_state.center_position.data[i], ir_names[i], 1.f));
195   }
196 
197   AddInput(new UndetectableAnalogInput<bool>(&m_ir_state.is_hidden, "IR Hidden", 1));
198 
199   AddInput(new UndetectableAnalogInput<float>(&m_ir_state.distance, "IR Distance", 1));
200 
201   // Raw gyroscope.
202   static constexpr std::array<std::array<const char*, 2>, 3> gyro_names = {{
203       {"Gyro Pitch Down", "Gyro Pitch Up"},
204       {"Gyro Roll Left", "Gyro Roll Right"},
205       {"Gyro Yaw Left", "Gyro Yaw Right"},
206   }};
207 
208   for (std::size_t i = 0; i != m_accel_data.data.size(); ++i)
209   {
210     AddInput(
211         new UndetectableAnalogInput<float>(&m_mplus_state.gyro_data.data[i], gyro_names[i][0], 1));
212     AddInput(
213         new UndetectableAnalogInput<float>(&m_mplus_state.gyro_data.data[i], gyro_names[i][1], -1));
214   }
215 
216   using WiimoteEmu::Nunchuk;
217   const std::string nunchuk_prefix = "Nunchuk ";
218 
219   // Buttons.
220   AddInput(new Button<u8>(&m_nunchuk_state.buttons, Nunchuk::BUTTON_C, nunchuk_prefix + "C"));
221   AddInput(new Button<u8>(&m_nunchuk_state.buttons, Nunchuk::BUTTON_Z, nunchuk_prefix + "Z"));
222 
223   // Stick.
224   static constexpr const char* const nunchuk_stick_names[] = {"X", "Y"};
225   for (std::size_t i = 0; i != std::size(nunchuk_stick_names); ++i)
226   {
227     AddInput(new SignedAnalogInput(&m_nunchuk_state.stick.data[i],
228                                    nunchuk_prefix + nunchuk_stick_names[i], -1.f));
229     AddInput(new SignedAnalogInput(&m_nunchuk_state.stick.data[i],
230                                    nunchuk_prefix + nunchuk_stick_names[i], 1.f));
231   }
232 
233   // Raw accelerometer.
234   for (std::size_t i = 0; i != m_accel_data.data.size(); ++i)
235   {
236     AddInput(new UndetectableAnalogInput<float>(&m_nunchuk_state.accel.data[i],
237                                                 nunchuk_prefix + accel_names[i][0], 1));
238     AddInput(new UndetectableAnalogInput<float>(&m_nunchuk_state.accel.data[i],
239                                                 nunchuk_prefix + accel_names[i][1], -1));
240   }
241 
242   using WiimoteEmu::Classic;
243   const std::string classic_prefix = "Classic ";
244 
245   // Buttons.
246   static constexpr u16 classic_dpad_masks[] = {
247       Classic::PAD_UP,
248       Classic::PAD_DOWN,
249       Classic::PAD_LEFT,
250       Classic::PAD_RIGHT,
251   };
252 
253   for (std::size_t i = 0; i != std::size(classic_dpad_masks); ++i)
254     AddInput(new Button<u16>(&m_classic_state.buttons, classic_dpad_masks[i],
255                              classic_prefix + named_directions[i]));
256 
257   static constexpr u16 classic_button_masks[] = {
258       Classic::BUTTON_A,     Classic::BUTTON_B,    Classic::BUTTON_X,    Classic::BUTTON_Y,
259       Classic::TRIGGER_L,    Classic::TRIGGER_R,   Classic::BUTTON_ZL,   Classic::BUTTON_ZR,
260       Classic::BUTTON_MINUS, Classic::BUTTON_PLUS, Classic::BUTTON_HOME,
261   };
262 
263   static constexpr const char* const classic_button_names[] = {
264       "A", "B", "X", "Y", "L", "R", "ZL", "ZR", "-", "+", "HOME",
265   };
266 
267   for (std::size_t i = 0; i != std::size(classic_button_masks); ++i)
268     AddInput(new Button<u16>(&m_classic_state.buttons, classic_button_masks[i],
269                              classic_prefix + classic_button_names[i]));
270 
271   // Sticks.
272   static constexpr const char* const classic_stick_names[][2] = {{"Left X", "Left Y"},
273                                                                  {"Right X", "Right Y"}};
274 
275   for (std::size_t s = 0; s != std::size(m_classic_state.sticks); ++s)
276   {
277     for (std::size_t i = 0; i != std::size(m_classic_state.sticks[0].data); ++i)
278     {
279       AddInput(new SignedAnalogInput(&m_classic_state.sticks[s].data[i],
280                                      classic_prefix + classic_stick_names[s][i], -1.f));
281       AddInput(new SignedAnalogInput(&m_classic_state.sticks[s].data[i],
282                                      classic_prefix + classic_stick_names[s][i], 1.f));
283     }
284   }
285 
286   // Triggers.
287   AddInput(new AnalogInput<float>(&m_classic_state.triggers[0], classic_prefix + "L-Analog", 1.f));
288   AddInput(new AnalogInput<float>(&m_classic_state.triggers[1], classic_prefix + "R-Analog", 1.f));
289 
290   // Specialty inputs:
291   AddInput(new UndetectableAnalogInput<float>(&m_battery, "Battery", 1.f));
292   AddInput(new UndetectableAnalogInput<WiimoteEmu::ExtensionNumber>(
293       &m_extension_number_input, "Attached Extension", WiimoteEmu::ExtensionNumber(1)));
294   AddInput(new UndetectableAnalogInput<bool>(&m_mplus_attached_input, "Attached MotionPlus", 1));
295 
296   AddOutput(new Motor(&m_rumble_level));
297 }
298 
~Device()299 Device::~Device()
300 {
301   if (!m_wiimote->IsConnected())
302     return;
303 
304   m_wiimote->EmuStop();
305 
306   INFO_LOG(WIIMOTE, "WiiRemote: Returning remote to pool.");
307   WiimoteReal::AddWiimoteToPool(std::move(m_wiimote));
308 }
309 
GetName() const310 std::string Device::GetName() const
311 {
312   return "Wii Remote";
313 }
314 
GetSource() const315 std::string Device::GetSource() const
316 {
317   return SOURCE_NAME;
318 }
319 
RunTasks()320 void Device::RunTasks()
321 {
322   if (IsPerformingTask())
323     return;
324 
325   // Request status.
326   if (Clock::now() >= m_status_outdated_time)
327   {
328     QueueReport(OutputReportRequestStatus());
329 
330     AddReportHandler(std::function<void(const InputReportStatus& status)>(
331         [this](const InputReportStatus& status) {
332           DEBUG_LOG(WIIMOTE, "WiiRemote: Received requested status.");
333           ProcessStatusReport(status);
334         }));
335 
336     return;
337   }
338 
339   // Set LEDs.
340   const auto desired_leds = GetDesiredLEDValue();
341   if (m_leds != desired_leds)
342   {
343     OutputReportLeds rpt = {};
344     rpt.ack = 1;
345     rpt.leds = desired_leds;
346     QueueReport(rpt, [this, desired_leds](ErrorCode result) {
347       if (result != ErrorCode::Success)
348       {
349         WARN_LOG(WIIMOTE, "WiiRemote: Failed to set LEDs.");
350         return;
351       }
352 
353       DEBUG_LOG(WIIMOTE, "WiiRemote: Set LEDs.");
354 
355       m_leds = desired_leds;
356     });
357 
358     return;
359   }
360 
361   // Set reporting mode to one that supports every component.
362   static constexpr auto desired_reporting_mode = InputReportID::ReportCoreAccelIR10Ext6;
363   if (m_reporting_mode != desired_reporting_mode)
364   {
365     OutputReportMode mode = {};
366     mode.ack = 1;
367     mode.mode = desired_reporting_mode;
368     QueueReport(mode, [this](ErrorCode error) {
369       if (error != ErrorCode::Success)
370       {
371         WARN_LOG(WIIMOTE, "WiiRemote: Failed to set reporting mode.");
372         return;
373       }
374 
375       m_reporting_mode = desired_reporting_mode;
376 
377       DEBUG_LOG(WIIMOTE, "WiiRemote: Set reporting mode.");
378     });
379 
380     return;
381   }
382 
383   // Read accelerometer calibration.
384   if (!m_accel_calibration.has_value())
385   {
386     static constexpr u16 ACCEL_CALIBRATION_ADDR = 0x16;
387 
388     ReadData(AddressSpace::EEPROM, 0, ACCEL_CALIBRATION_ADDR, sizeof(AccelCalibrationData),
389              [this](ReadResponse response) {
390                if (!response)
391                {
392                  WARN_LOG(WIIMOTE, "WiiRemote: Failed to read accelerometer calibration.");
393                  return;
394                }
395 
396                DEBUG_LOG(WIIMOTE, "WiiRemote: Read accelerometer calibration.");
397 
398                auto& calibration_data = *response;
399 
400                const AccelCalibrationData accel_calibration =
401                    Common::BitCastPtr<AccelCalibrationData>(calibration_data.data());
402                m_accel_calibration = accel_calibration.GetCalibration();
403 
404                WiimoteEmu::UpdateCalibrationDataChecksum(calibration_data, 1);
405 
406                // We could potentially try the second block at 0x26 if the checksum is bad.
407                if (accel_calibration.checksum != calibration_data.back())
408                  WARN_LOG(WIIMOTE, "WiiRemote: Bad accelerometer calibration checksum.");
409              });
410 
411     return;
412   }
413 
414   if (!m_ir_state.IsFullyConfigured())
415   {
416     ConfigureIRCamera();
417 
418     return;
419   }
420 
421   if (!m_speaker_configured)
422   {
423     ConfigureSpeaker();
424 
425     return;
426   }
427 
428   // Perform the following tasks only after M+ is settled.
429   if (IsWaitingForMotionPlus())
430     return;
431 
432   // Read the "active" extension ID. (This also gives us the current M+ mode)
433   // This will fail on an un-intialized other extension.
434   // But extension initialization is the same as M+ de-activation so we must try this first.
435   if (m_extension_port == true &&
436       (!IsMotionPlusStateKnown() || (!IsMotionPlusActive() && !m_extension_id.has_value())))
437   {
438     static constexpr u16 ENCRYPTION_ADDR = 0xfb;
439     static constexpr u8 ENCRYPTION_VALUE = 0x00;
440 
441     // First disable encryption. Note this is a no-op when performed on the M+.
442     WriteData(AddressSpace::I2CBus, WiimoteEmu::ExtensionPort::REPORT_I2C_SLAVE, ENCRYPTION_ADDR,
443               {ENCRYPTION_VALUE}, [this](ErrorCode error) {
444                 if (error != ErrorCode::Success)
445                   return;
446 
447                 ReadActiveExtensionID();
448               });
449 
450     return;
451   }
452 
453   static constexpr u16 INIT_ADDR = 0xf0;
454   static constexpr u8 INIT_VALUE = 0x55;
455 
456   // Initialize "active" extension if ID was not recognized.
457   // Note this is done before M+ setup to determine the required passthrough mode.
458   if (m_extension_id == ExtensionID::Unsupported)
459   {
460     // Note that this signal also DE-activates a M+.
461     WriteData(AddressSpace::I2CBus, WiimoteEmu::ExtensionPort::REPORT_I2C_SLAVE, INIT_ADDR,
462               {INIT_VALUE}, [this](ErrorCode result) {
463                 DEBUG_LOG(WIIMOTE, "WiiRemote: Initialized extension: %d.", int(result));
464 
465                 m_extension_id = std::nullopt;
466               });
467 
468     return;
469   }
470 
471   // The following tasks require a known M+ state.
472   if (!IsMotionPlusStateKnown())
473     return;
474 
475   // We now know the status of the M+.
476   // Updating it too frequently results off/on flashes on mode change.
477   m_mplus_attached_input = IsMotionPlusActive();
478 
479   // Extension removal status is known here. Attachment status is updated after the ID is read.
480   if (m_extension_port != true)
481     m_extension_number_input = WiimoteEmu::ExtensionNumber::NONE;
482 
483   // Periodically try to initialize and activate an inactive M+.
484   if (!IsMotionPlusActive() && m_mplus_desired_mode.has_value() &&
485       m_mplus_state.current_mode != m_mplus_desired_mode)
486   {
487     static constexpr u16 MPLUS_POLL_ADDR = WiimoteEmu::MotionPlus::PASSTHROUGH_MODE_OFFSET;
488     ReadData(AddressSpace::I2CBus, WiimoteEmu::MotionPlus::INACTIVE_DEVICE_ADDR, MPLUS_POLL_ADDR, 1,
489              [this](ReadResponse response) {
490                if (!response)
491                {
492                  DEBUG_LOG(WIIMOTE, "WiiRemote: M+ poll failed.");
493                  HandleMotionPlusNonResponse();
494                  return;
495                }
496 
497                WriteData(AddressSpace::I2CBus, WiimoteEmu::MotionPlus::INACTIVE_DEVICE_ADDR,
498                          INIT_ADDR, {INIT_VALUE}, [this](ErrorCode result) {
499                            DEBUG_LOG(WIIMOTE, "WiiRemote: M+ initialization: %d.", int(result));
500                            if (result != ErrorCode::Success)
501                            {
502                              HandleMotionPlusNonResponse();
503                              return;
504                            }
505 
506                            TriggerMotionPlusModeChange();
507                          });
508              });
509 
510     return;
511   }
512 
513   // Change active M+ passthrough mode.
514   if (IsMotionPlusActive() && m_mplus_desired_mode.has_value() &&
515       m_mplus_state.current_mode != m_mplus_desired_mode)
516   {
517     TriggerMotionPlusModeChange();
518 
519     return;
520   }
521 
522   // Read passthrough extension ID.
523   // This will also give us a desired M+ passthrough mode.
524   if (IsMotionPlusActive() && m_mplus_state.passthrough_port == true && !m_extension_id.has_value())
525   {
526     // The M+ reads the passthrough ext ID and stores it at 0xf6,f8,f9.
527     static constexpr u16 PASSTHROUGH_EXT_ID_ADDR = 0xf6;
528 
529     ReadData(AddressSpace::I2CBus, WiimoteEmu::MotionPlus::ACTIVE_DEVICE_ADDR,
530              PASSTHROUGH_EXT_ID_ADDR, 4, [this](ReadResponse response) {
531                if (!response)
532                  return;
533 
534                // Port status may have changed since the read was sent.
535                // In which case this data read would succeed but be useless.
536                if (m_mplus_state.passthrough_port != true)
537                  return;
538 
539                auto& identifier = *response;
540 
541                ProcessExtensionID(identifier[2], identifier[0], identifier[3]);
542              });
543 
544     return;
545   }
546 
547   // The following tasks require M+ configuration to be done.
548   if (!IsMotionPlusInDesiredMode())
549     return;
550 
551   // Now that M+ config has settled we can update the extension number.
552   // Updating it too frequently results off/on flashes on M+ mode change.
553   UpdateExtensionNumberInput();
554 
555   static constexpr u16 NORMAL_CALIBRATION_ADDR = 0x20;
556 
557   // Read M+ calibration.
558   if (IsMotionPlusActive() && !m_mplus_state.calibration.has_value())
559   {
560     ReadData(AddressSpace::I2CBus, WiimoteEmu::MotionPlus::ACTIVE_DEVICE_ADDR,
561              NORMAL_CALIBRATION_ADDR, sizeof(WiimoteEmu::MotionPlus::CalibrationData),
562              [this](ReadResponse response) {
563                if (!response)
564                  return;
565 
566                DEBUG_LOG(WIIMOTE, "WiiRemote: Read M+ calibration.");
567 
568                WiimoteEmu::MotionPlus::CalibrationData calibration =
569                    Common::BitCastPtr<WiimoteEmu::MotionPlus::CalibrationData>(response->data());
570 
571                const auto read_checksum = std::pair(calibration.crc32_lsb, calibration.crc32_msb);
572 
573                calibration.UpdateChecksum();
574 
575                m_mplus_state.SetCalibrationData(calibration);
576 
577                if (read_checksum != std::pair(calibration.crc32_lsb, calibration.crc32_msb))
578                {
579                  // We could potentially try another read or call the M+ unusable.
580                  WARN_LOG(WIIMOTE, "WiiRemote: Bad M+ calibration checksum.");
581                }
582              });
583 
584     return;
585   }
586 
587   // Read normal extension calibration.
588   if ((m_extension_id == ExtensionID::Nunchuk && !m_nunchuk_state.calibration) ||
589       (m_extension_id == ExtensionID::Classic && !m_classic_state.calibration))
590   {
591     // Extension calibration is normally at 0x20 but M+ reads and stores it at 0x40.
592     static constexpr u16 PASSTHROUGH_CALIBRATION_ADDR = 0x40;
593 
594     const u16 calibration_addr =
595         IsMotionPlusActive() ? PASSTHROUGH_CALIBRATION_ADDR : NORMAL_CALIBRATION_ADDR;
596     static constexpr u16 CALIBRATION_SIZE = 0x10;
597 
598     ReadData(
599         AddressSpace::I2CBus, WiimoteEmu::ExtensionPort::REPORT_I2C_SLAVE, calibration_addr,
600         CALIBRATION_SIZE, [this](ReadResponse response) {
601           if (!response)
602             return;
603 
604           DEBUG_LOG(WIIMOTE, "WiiRemote: Read extension calibration.");
605 
606           auto& calibration_data = *response;
607 
608           const auto read_checksum = std::pair(calibration_data[CALIBRATION_SIZE - 2],
609                                                calibration_data[CALIBRATION_SIZE - 1]);
610 
611           WiimoteEmu::UpdateCalibrationDataChecksum(calibration_data, 2);
612 
613           Checksum checksum = Checksum::Good;
614 
615           if (read_checksum != std::pair(calibration_data[CALIBRATION_SIZE - 2],
616                                          calibration_data[CALIBRATION_SIZE - 1]))
617           {
618             // We could potentially try another block or call the extension unusable.
619             WARN_LOG(WIIMOTE, "WiiRemote: Bad extension calibration checksum.");
620             checksum = Checksum::Bad;
621           }
622 
623           if (m_extension_id == ExtensionID::Nunchuk)
624           {
625             m_nunchuk_state.SetCalibrationData(
626                 Common::BitCastPtr<WiimoteEmu::Nunchuk::CalibrationData>(calibration_data.data()),
627                 checksum);
628           }
629           else if (m_extension_id == ExtensionID::Classic)
630           {
631             m_classic_state.SetCalibrationData(
632                 Common::BitCastPtr<WiimoteEmu::Classic::CalibrationData>(calibration_data.data()),
633                 checksum);
634           }
635         });
636 
637     return;
638   }
639 }
640 
HandleMotionPlusNonResponse()641 void Device::HandleMotionPlusNonResponse()
642 {
643   // No need for additional checks if an extension is attached.
644   // (not possible for M+ to become attached)
645   if (m_extension_port == true)
646     m_mplus_desired_mode = MotionPlusState::PassthroughMode{};
647   else
648     WaitForMotionPlus();
649 }
650 
651 // Produce LED bitmask for remotes.
652 // Remotes 1-4 are normal. Additional remotes LED labels will add up to their assigned ID.
GetDesiredLEDValue() const653 u8 Device::GetDesiredLEDValue() const
654 {
655   const auto index = GetId();
656 
657   // Normal LED behavior for remotes 1-4.
658   if (index < 4)
659     return 1 << index;
660 
661   // Light LED 4 and LEDs 1 through 3 for remotes 5-7. (Add up the numbers on the remote)
662   if (index < 7)
663     return 1 << (index - 4) | 8;
664 
665   // Light LED 4+3 and LEDs 1 or 2 for remotes 8 or 9. (Add up the numbers on the remote)
666   if (index < 9)
667     return 1 << (index - 7) | 8 | 4;
668 
669   // For remotes 10 and up just light all LEDs.
670   return 0xf;
671 }
672 
UpdateExtensionNumberInput()673 void Device::UpdateExtensionNumberInput()
674 {
675   switch (m_extension_id.value_or(ExtensionID::Unsupported))
676   {
677   case ExtensionID::Nunchuk:
678     m_extension_number_input = WiimoteEmu::ExtensionNumber::NUNCHUK;
679     break;
680   case ExtensionID::Classic:
681     m_extension_number_input = WiimoteEmu::ExtensionNumber::CLASSIC;
682     break;
683   case ExtensionID::Unsupported:
684   default:
685     m_extension_number_input = WiimoteEmu::ExtensionNumber::NONE;
686     break;
687   }
688 }
689 
ProcessExtensionEvent(bool connected)690 void Device::ProcessExtensionEvent(bool connected)
691 {
692   // Reset extension state.
693   m_nunchuk_state = {};
694   m_classic_state = {};
695 
696   m_extension_id = std::nullopt;
697 
698   // We won't know the desired mode until we get the extension ID.
699   if (connected)
700     m_mplus_desired_mode = std::nullopt;
701 }
702 
ProcessExtensionID(u8 id_0,u8 id_4,u8 id_5)703 void Device::ProcessExtensionID(u8 id_0, u8 id_4, u8 id_5)
704 {
705   if (id_4 == 0x00 && id_5 == 0x00)
706   {
707     INFO_LOG(WIIMOTE, "WiiRemote: Nunchuk is attached.");
708     m_extension_id = ExtensionID::Nunchuk;
709 
710     m_mplus_desired_mode = MotionPlusState::PassthroughMode::Nunchuk;
711   }
712   else if (id_4 == 0x01 && id_5 == 0x01)
713   {
714     INFO_LOG(WIIMOTE, "WiiRemote: Classic Controller is attached.");
715     m_extension_id = ExtensionID::Classic;
716 
717     m_mplus_desired_mode = MotionPlusState::PassthroughMode::Classic;
718   }
719   else
720   {
721     // This is a normal occurance before extension initialization.
722     DEBUG_LOG(WIIMOTE, "WiiRemote: Unknown extension: %d %d %d.", id_0, id_4, id_5);
723     m_extension_id = ExtensionID::Unsupported;
724   }
725 }
726 
SetCalibrationData(const WiimoteEmu::MotionPlus::CalibrationData & data)727 void Device::MotionPlusState::SetCalibrationData(
728     const WiimoteEmu::MotionPlus::CalibrationData& data)
729 {
730   DEBUG_LOG(WIIMOTE, "WiiRemote: Set M+ calibration.");
731 
732   calibration.emplace();
733 
734   calibration->fast = data.fast;
735   calibration->slow = data.slow;
736 }
737 
Calibration()738 Device::NunchukState::Calibration::Calibration() : accel{}, stick{}
739 {
740   accel.zero.data.fill(1 << (accel.BITS_OF_PRECISION - 1));
741   // Approximate 1G value per WiiBrew:
742   accel.max.data.fill(740);
743 
744   stick.zero.data.fill(1 << (stick.BITS_OF_PRECISION - 1));
745   stick.max.data.fill((1 << stick.BITS_OF_PRECISION) - 1);
746 }
747 
SetCalibrationData(const WiimoteEmu::Nunchuk::CalibrationData & data,Checksum checksum)748 void Device::NunchukState::SetCalibrationData(const WiimoteEmu::Nunchuk::CalibrationData& data,
749                                               Checksum checksum)
750 {
751   DEBUG_LOG(WIIMOTE, "WiiRemote: Set Nunchuk calibration.");
752 
753   calibration.emplace();
754 
755   if (checksum == Checksum::Bad)
756     return;
757 
758   // Genuine Nunchuks have been observed with "min" and "max" values of zero.
759   // We catch that here and fall back to "full range" calibration.
760   const auto stick_calibration = data.GetStick();
761   if (stick_calibration.IsSane())
762     calibration->stick = stick_calibration;
763   else
764     WARN_LOG(WIIMOTE, "WiiRemote: Nunchuk stick calibration is not sane. Using fallback values.");
765 
766   // No known reports of bad accelerometer calibration but we'll handle it just in case.
767   const auto accel_calibration = data.GetAccel();
768   if (accel_calibration.IsSane())
769     calibration->accel = accel_calibration;
770   else
771     WARN_LOG(WIIMOTE, "WiiRemote: Nunchuk accel calibration is not sane. Using fallback values.");
772 }
773 
Calibration()774 Device::ClassicState::Calibration::Calibration()
775     : left_stick{}, right_stick{}, left_trigger{}, right_trigger{}
776 {
777   left_stick.zero.data.fill(1 << (left_stick.BITS_OF_PRECISION - 1));
778   left_stick.max.data.fill((1 << left_stick.BITS_OF_PRECISION) - 1);
779 
780   right_stick.zero.data.fill(1 << (right_stick.BITS_OF_PRECISION - 1));
781   right_stick.max.data.fill((1 << right_stick.BITS_OF_PRECISION) - 1);
782 
783   left_trigger.max = (1 << left_trigger.BITS_OF_PRECISION) - 1;
784   right_trigger.max = (1 << right_trigger.BITS_OF_PRECISION) - 1;
785 }
786 
SetCalibrationData(const WiimoteEmu::Classic::CalibrationData & data,Checksum checksum)787 void Device::ClassicState::SetCalibrationData(const WiimoteEmu::Classic::CalibrationData& data,
788                                               Checksum checksum)
789 {
790   DEBUG_LOG(WIIMOTE, "WiiRemote: Set Classic Controller calibration.");
791 
792   calibration.emplace();
793 
794   if (checksum == Checksum::Bad)
795     return;
796 
797   const auto left_stick_calibration = data.GetLeftStick();
798   if (left_stick_calibration.IsSane())
799     calibration->left_stick = left_stick_calibration;
800   else
801     WARN_LOG(WIIMOTE, "WiiRemote: CC left stick calibration is not sane. Using fallback values.");
802 
803   const auto right_stick_calibration = data.GetRightStick();
804   if (right_stick_calibration.IsSane())
805     calibration->right_stick = right_stick_calibration;
806   else
807     WARN_LOG(WIIMOTE, "WiiRemote: CC right stick calibration is not sane. Using fallback values.");
808 
809   calibration->left_trigger = data.GetLeftTrigger();
810   calibration->right_trigger = data.GetRightTrigger();
811 }
812 
ReadActiveExtensionID()813 void Device::ReadActiveExtensionID()
814 {
815   static constexpr u16 EXT_ID_ADDR = 0xfa;
816   static constexpr u16 EXT_ID_SIZE = 6;
817 
818   ReadData(AddressSpace::I2CBus, WiimoteEmu::ExtensionPort::REPORT_I2C_SLAVE, EXT_ID_ADDR,
819            EXT_ID_SIZE, [this](ReadResponse response) {
820              if (!response)
821                return;
822 
823              auto& identifier = *response;
824 
825              // Check for M+ ID.
826              if (identifier[5] == 0x05)
827              {
828                const auto passthrough_mode = MotionPlusState::PassthroughMode(identifier[4]);
829 
830                m_mplus_state.current_mode = passthrough_mode;
831 
832                INFO_LOG(WIIMOTE, "WiiRemote: M+ is active in mode: %d.", int(passthrough_mode));
833              }
834              else
835              {
836                m_mplus_state.current_mode = MotionPlusState::PassthroughMode{};
837 
838                ProcessExtensionID(identifier[0], identifier[4], identifier[5]);
839              }
840            });
841 }
842 
IsFullyConfigured() const843 bool Device::IRState::IsFullyConfigured() const
844 {
845   return enabled && mode_set && current_sensitivity == GetDesiredIRSensitivity();
846 }
847 
GetDesiredIRSensitivity()848 u32 Device::IRState::GetDesiredIRSensitivity()
849 {
850   // Wii stores values from 1 to 5. (subtract 1)
851   const u32 configured_level = Config::Get(Config::SYSCONF_SENSOR_BAR_SENSITIVITY) - 1;
852 
853   if (configured_level < IR_SENSITIVITY_LEVEL_COUNT)
854     return configured_level;
855 
856   // Default to middle level on bad value.
857   return 2;
858 }
859 
SetIRSensitivity(u32 level)860 void Device::SetIRSensitivity(u32 level)
861 {
862   struct IRSensitivityConfig
863   {
864     std::array<u8, 9> block1;
865     std::array<u8, 2> block2;
866   };
867 
868   // Data for Wii levels 1 to 5.
869   static constexpr std::array<IRSensitivityConfig, IR_SENSITIVITY_LEVEL_COUNT> sensitivity_configs =
870       {{
871           {{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0x64, 0x00, 0xfe}, {0xfd, 0x05}},
872           {{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0x96, 0x00, 0xb4}, {0xb3, 0x04}},
873           {{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xaa, 0x00, 0x64}, {0x63, 0x03}},
874           {{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xc8, 0x00, 0x36}, {0x35, 0x03}},
875           {{0x07, 0x00, 0x00, 0x71, 0x01, 0x00, 0x72, 0x00, 0x20}, {0x1f, 0x03}},
876       }};
877 
878   static constexpr u16 BLOCK1_ADDR = 0x00;
879   static constexpr u16 BLOCK2_ADDR = 0x1a;
880 
881   DEBUG_LOG(WIIMOTE, "WiiRemote: Setting IR sensitivity: %d.", level + 1);
882 
883   const auto& sensitivity_config = sensitivity_configs[level];
884 
885   WriteData(AddressSpace::I2CBus, WiimoteEmu::CameraLogic::I2C_ADDR, BLOCK1_ADDR,
886             sensitivity_config.block1, [&sensitivity_config, level, this](ErrorCode block_result) {
887               if (block_result != ErrorCode::Success)
888               {
889                 WARN_LOG(WIIMOTE, "WiiRemote: Failed to write IR block 1.");
890                 return;
891               }
892 
893               WriteData(AddressSpace::I2CBus, WiimoteEmu::CameraLogic::I2C_ADDR, BLOCK2_ADDR,
894                         sensitivity_config.block2, [&, level, this](ErrorCode block2_result) {
895                           if (block2_result != ErrorCode::Success)
896                           {
897                             WARN_LOG(WIIMOTE, "WiiRemote: Failed to write IR block 2.");
898                             return;
899                           }
900 
901                           DEBUG_LOG(WIIMOTE, "WiiRemote: IR sensitivity set.");
902 
903                           m_ir_state.current_sensitivity = level;
904                         });
905             });
906 }
907 
ConfigureIRCamera()908 void Device::ConfigureIRCamera()
909 {
910   if (!m_ir_state.enabled)
911   {
912     OutputReportIRLogicEnable2 ir_logic2 = {};
913     ir_logic2.ack = 1;
914     ir_logic2.enable = 1;
915     QueueReport(ir_logic2, [this](ErrorCode result) {
916       if (result != ErrorCode::Success)
917       {
918         WARN_LOG(WIIMOTE, "WiiRemote: Failed to enable IR.");
919         return;
920       }
921 
922       OutputReportIRLogicEnable ir_logic = {};
923       ir_logic.ack = 1;
924       ir_logic.enable = 1;
925       QueueReport(ir_logic, [this](ErrorCode ir_result) {
926         if (ir_result != ErrorCode::Success)
927         {
928           WARN_LOG(WIIMOTE, "WiiRemote: Failed to enable IR.");
929           return;
930         }
931 
932         DEBUG_LOG(WIIMOTE, "WiiRemote: IR enabled.");
933 
934         m_ir_state.enabled = true;
935       });
936     });
937 
938     return;
939   }
940 
941   if (const u32 desired_level = IRState::GetDesiredIRSensitivity();
942       desired_level != m_ir_state.current_sensitivity)
943   {
944     SetIRSensitivity(desired_level);
945 
946     return;
947   }
948 
949   if (!m_ir_state.mode_set)
950   {
951     static constexpr u16 MODE_ADDR = 0x33;
952 
953     // We only support "Basic" mode (it's all that fits in ReportCoreAccelIR10Ext6).
954     WriteData(AddressSpace::I2CBus, WiimoteEmu::CameraLogic::I2C_ADDR, MODE_ADDR,
955               {WiimoteEmu::CameraLogic::IR_MODE_BASIC}, [this](ErrorCode mode_result) {
956                 if (mode_result != ErrorCode::Success)
957                 {
958                   WARN_LOG(WIIMOTE, "WiiRemote: Failed to set IR mode.");
959                   return;
960                 }
961 
962                 // This seems to enable object tracking.
963                 static constexpr u16 ENABLE_ADDR = 0x30;
964                 static constexpr u8 ENABLE_VALUE = 0x08;
965 
966                 WriteData(AddressSpace::I2CBus, WiimoteEmu::CameraLogic::I2C_ADDR, ENABLE_ADDR,
967                           {ENABLE_VALUE}, [this](ErrorCode result) {
968                             if (result != ErrorCode::Success)
969                             {
970                               WARN_LOG(WIIMOTE, "WiiRemote: Failed to enable object tracking.");
971                               return;
972                             }
973 
974                             DEBUG_LOG(WIIMOTE, "WiiRemote: IR mode set.");
975 
976                             m_ir_state.mode_set = true;
977                           });
978               });
979   }
980 }
981 
ConfigureSpeaker()982 void Device::ConfigureSpeaker()
983 {
984   OutputReportSpeakerMute mute = {};
985   mute.enable = 1;
986   mute.ack = 1;
987   QueueReport(mute, [this](ErrorCode mute_result) {
988     if (mute_result != ErrorCode::Success)
989     {
990       WARN_LOG(WIIMOTE, "WiiRemote: Failed to mute speaker.");
991       return;
992     }
993 
994     OutputReportSpeakerEnable spkr = {};
995     spkr.enable = 0;
996     spkr.ack = 1;
997     QueueReport(spkr, [this](ErrorCode enable_result) {
998       if (enable_result != ErrorCode::Success)
999       {
1000         WARN_LOG(WIIMOTE, "WiiRemote: Failed to disable speaker.");
1001         return;
1002       }
1003 
1004       DEBUG_LOG(WIIMOTE, "WiiRemote: Speaker muted and disabled.");
1005 
1006       m_speaker_configured = true;
1007     });
1008   });
1009 }
1010 
TriggerMotionPlusModeChange()1011 void Device::TriggerMotionPlusModeChange()
1012 {
1013   if (!m_mplus_desired_mode.has_value())
1014     return;
1015 
1016   const u8 passthrough_mode = u8(*m_mplus_desired_mode);
1017 
1018   const u8 device_addr = IsMotionPlusActive() ? WiimoteEmu::MotionPlus::ACTIVE_DEVICE_ADDR :
1019                                                 WiimoteEmu::MotionPlus::INACTIVE_DEVICE_ADDR;
1020 
1021   WriteData(AddressSpace::I2CBus, device_addr, WiimoteEmu::MotionPlus::PASSTHROUGH_MODE_OFFSET,
1022             {passthrough_mode}, [this](ErrorCode activation_result) {
1023               DEBUG_LOG(WIIMOTE, "WiiRemote: M+ activation: %d.", int(activation_result));
1024 
1025               WaitForMotionPlus();
1026 
1027               // Normally M+ will be seen performing a reset here. (extension port events)
1028               // But sometimes (rarely) M+ activation does not cause an extension port event.
1029               // We'll consider the mode unknown. It will be read back after some time.
1030               m_mplus_state.current_mode = std::nullopt;
1031             });
1032 }
1033 
TriggerMotionPlusCalibration()1034 void Device::TriggerMotionPlusCalibration()
1035 {
1036   static constexpr u16 CALIBRATION_TRIGGER_ADDR = 0xf2;
1037   static constexpr u8 CALIBRATION_TRIGGER_VALUE = 0x00;
1038 
1039   // This triggers a hardware "zero" calibration.
1040   // The effect is notiecable but output still strays from calibration data.
1041   // It seems we're better off just manually determining "zero".
1042   WriteData(AddressSpace::I2CBus, WiimoteEmu::MotionPlus::ACTIVE_DEVICE_ADDR,
1043             CALIBRATION_TRIGGER_ADDR, {CALIBRATION_TRIGGER_VALUE}, [](ErrorCode result) {
1044               DEBUG_LOG(WIIMOTE, "WiiRemote: M+ calibration trigger done: %d.", int(result));
1045             });
1046 }
1047 
IsMotionPlusStateKnown() const1048 bool Device::IsMotionPlusStateKnown() const
1049 {
1050   return m_mplus_state.current_mode.has_value();
1051 }
1052 
IsMotionPlusActive() const1053 bool Device::IsMotionPlusActive() const
1054 {
1055   return m_mplus_state.current_mode != MotionPlusState::PassthroughMode{};
1056 }
1057 
IsMotionPlusInDesiredMode() const1058 bool Device::IsMotionPlusInDesiredMode() const
1059 {
1060   return m_mplus_state.current_mode.has_value() &&
1061          (m_mplus_state.current_mode == m_mplus_desired_mode);
1062 }
1063 
ProcessInputReport(WiimoteReal::Report & report)1064 void Device::ProcessInputReport(WiimoteReal::Report& report)
1065 {
1066   if (report.size() < WiimoteReal::REPORT_HID_HEADER_SIZE)
1067   {
1068     WARN_LOG(WIIMOTE, "WiiRemote: Bad report size.");
1069     return;
1070   }
1071 
1072   auto report_id = InputReportID(report[WiimoteReal::REPORT_HID_HEADER_SIZE]);
1073 
1074   for (auto it = m_report_handlers.begin(); true;)
1075   {
1076     if (it == m_report_handlers.end())
1077     {
1078       if (report_id == InputReportID::Status)
1079       {
1080         if (report.size() - WiimoteReal::REPORT_HID_HEADER_SIZE <
1081             sizeof(TypedInputData<InputReportStatus>))
1082         {
1083           WARN_LOG(WIIMOTE, "WiiRemote: Bad report size.");
1084         }
1085         else
1086         {
1087           ProcessStatusReport(Common::BitCastPtr<InputReportStatus>(report.data() + 2));
1088         }
1089       }
1090       else if (report_id < InputReportID::ReportCore)
1091       {
1092         WARN_LOG(WIIMOTE, "WiiRemote: Unhandled input report: %s.",
1093                  ArrayToString(report.data(), u32(report.size())).c_str());
1094       }
1095 
1096       break;
1097     }
1098 
1099     if (it->IsExpired())
1100     {
1101       WARN_LOG(WIIMOTE, "WiiRemote: Removing expired handler.");
1102       it = m_report_handlers.erase(it);
1103       continue;
1104     }
1105 
1106     if (const auto result = it->TryToHandleReport(report);
1107         result == ReportHandler::HandlerResult::Handled)
1108     {
1109       it = m_report_handlers.erase(it);
1110       break;
1111     }
1112 
1113     ++it;
1114   }
1115 
1116   if (report_id < InputReportID::ReportCore)
1117   {
1118     // Normal input reports can be processed as "ReportCore".
1119     report_id = InputReportID::ReportCore;
1120   }
1121   else
1122   {
1123     // We can assume the last received input report is the current reporting mode.
1124     // FYI: This logic fails to properly handle the (never used) "interleaved" reports.
1125     m_reporting_mode = InputReportID(report_id);
1126   }
1127 
1128   auto manipulator = MakeDataReportManipulator(
1129       report_id, report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + sizeof(InputReportID));
1130 
1131   if (manipulator->GetDataSize() + WiimoteReal::REPORT_HID_HEADER_SIZE > report.size())
1132   {
1133     WARN_LOG(WIIMOTE, "WiiRemote: Bad report size.");
1134     return;
1135   }
1136 
1137   // Read buttons.
1138   manipulator->GetCoreData(&m_core_data);
1139 
1140   // Process accel data.
1141   if (manipulator->HasAccel() && m_accel_calibration.has_value())
1142   {
1143     // FYI: This logic fails to properly handle the (never used) "interleaved" reports.
1144     AccelData accel_data = {};
1145     manipulator->GetAccelData(&accel_data);
1146 
1147     m_accel_data =
1148         accel_data.GetNormalizedValue(*m_accel_calibration) * float(MathUtil::GRAVITY_ACCELERATION);
1149   }
1150 
1151   // Process IR data.
1152   if (manipulator->HasIR() && m_ir_state.IsFullyConfigured())
1153   {
1154     m_ir_state.ProcessData(
1155         Common::BitCastPtr<std::array<WiimoteEmu::IRBasic, 2>>(manipulator->GetIRDataPtr()));
1156   }
1157 
1158   // Process extension data.
1159   if (IsMotionPlusStateKnown())
1160   {
1161     const auto ext_data = manipulator->GetExtDataPtr();
1162     const auto ext_size = manipulator->GetExtDataSize();
1163 
1164     if (IsMotionPlusActive())
1165       ProcessMotionPlusExtensionData(ext_data, ext_size);
1166     else
1167       ProcessNormalExtensionData(ext_data, ext_size);
1168   }
1169 
1170   UpdateOrientation();
1171 }
1172 
UpdateOrientation()1173 void Device::UpdateOrientation()
1174 {
1175   const auto current_report_time = Clock::now();
1176   const auto elapsed_time = std::chrono::duration_cast<std::chrono::duration<float>>(
1177       current_report_time - m_last_report_time);
1178   m_last_report_time = current_report_time;
1179 
1180   // Apply M+ gyro data to our orientation.
1181   m_orientation =
1182       WiimoteEmu::GetMatrixFromGyroscope(m_mplus_state.gyro_data * -1 * elapsed_time.count()) *
1183       m_orientation;
1184 
1185   // When M+ data is not available give accel/ir data more weight.
1186   // ComplementaryFilter will then just smooth out our data a bit.
1187   const bool is_mplus_active = IsMotionPlusStateKnown() && IsMotionPlusActive();
1188 
1189   // With non-zero acceleration data we can perform pitch and roll correction.
1190   if (m_accel_data.LengthSquared())
1191   {
1192     const auto accel_weight = is_mplus_active ? 0.04 : 0.5f;
1193 
1194     m_orientation = WiimoteEmu::ComplementaryFilter(m_orientation, m_accel_data, accel_weight);
1195   }
1196 
1197   // If IR objects are visible we can perform yaw and pitch correction.
1198   if (!m_ir_state.is_hidden)
1199   {
1200     // FYI: We could do some roll correction from multiple IR objects.
1201 
1202     const auto ir_rotation =
1203         Common::Vec3(m_ir_state.center_position.y * WiimoteEmu::CameraLogic::CAMERA_FOV_Y, 0,
1204                      m_ir_state.center_position.x * WiimoteEmu::CameraLogic::CAMERA_FOV_X) /
1205         2;
1206     const auto ir_normal = Common::Vec3(0, 1, 0);
1207     const auto ir_vector = WiimoteEmu::GetMatrixFromGyroscope(-ir_rotation) * ir_normal;
1208 
1209     // Pitch correction will be slightly wrong based on sensorbar height.
1210     // Keep weight below accelerometer weight for that reason.
1211     // Correction will only happen near pitch zero when the sensorbar is actually in view.
1212     const auto ir_weight = is_mplus_active ? 0.035 : 0.45f;
1213 
1214     m_orientation = WiimoteEmu::ComplementaryFilter(m_orientation, ir_vector, ir_weight, ir_normal);
1215   }
1216 
1217   // Update our (pitch, roll, yaw) inputs now that orientation has been adjusted.
1218   m_rotation_inputs =
1219       Common::Vec3{WiimoteEmu::GetPitch(m_orientation), WiimoteEmu::GetRoll(m_orientation),
1220                    WiimoteEmu::GetYaw(m_orientation)} /
1221       float(MathUtil::PI);
1222 }
1223 
ProcessData(const std::array<WiimoteEmu::IRBasic,2> & data)1224 void Device::IRState::ProcessData(const std::array<WiimoteEmu::IRBasic, 2>& data)
1225 {
1226   // A better implementation might extrapolate points when they fall out of camera view.
1227   // But just averaging visible points actually seems to work very well.
1228 
1229   using IRObject = WiimoteEmu::IRBasic::IRObject;
1230 
1231   MathUtil::RunningVariance<Common::Vec2> points;
1232 
1233   const auto camera_max = IRObject(WiimoteEmu::CameraLogic::CAMERA_RES_X - 1,
1234                                    WiimoteEmu::CameraLogic::CAMERA_RES_Y - 1);
1235 
1236   const auto add_point = [&](IRObject point) {
1237     // Non-visible points are 0xFF-filled.
1238     if (point.y > camera_max.y)
1239       return;
1240 
1241     points.Push(Common::Vec2(point));
1242   };
1243 
1244   for (auto& block : data)
1245   {
1246     add_point(block.GetObject1());
1247     add_point(block.GetObject2());
1248   }
1249 
1250   is_hidden = !points.Count();
1251 
1252   if (points.Count() >= 2)
1253   {
1254     const auto variance = points.PopulationVariance();
1255     // Adjusts Y coorinate to match horizontal FOV.
1256     const auto separation =
1257         Common::Vec2(std::sqrt(variance.x), std::sqrt(variance.y)) /
1258         Common::Vec2(WiimoteEmu::CameraLogic::CAMERA_RES_X,
1259                      WiimoteEmu::CameraLogic::CAMERA_RES_Y * WiimoteEmu::CameraLogic::CAMERA_AR) *
1260         2;
1261 
1262     distance = WiimoteEmu::CameraLogic::SENSOR_BAR_LED_SEPARATION / separation.Length() / 2 /
1263                std::tan(WiimoteEmu::CameraLogic::CAMERA_FOV_X / 2);
1264   }
1265 
1266   if (points.Count())
1267   {
1268     center_position = points.Mean() / Common::Vec2(camera_max) * 2.f - Common::Vec2(1, 1);
1269   }
1270   else
1271   {
1272     center_position = {};
1273   }
1274 }
1275 
ProcessMotionPlusExtensionData(const u8 * ext_data,u32 ext_size)1276 void Device::ProcessMotionPlusExtensionData(const u8* ext_data, u32 ext_size)
1277 {
1278   if (ext_size < sizeof(WiimoteEmu::MotionPlus::DataFormat))
1279     return;
1280 
1281   const WiimoteEmu::MotionPlus::DataFormat mplus_data =
1282       Common::BitCastPtr<WiimoteEmu::MotionPlus::DataFormat>(ext_data);
1283 
1284   const bool is_ext_connected = mplus_data.extension_connected;
1285 
1286   // Handle passthrough extension change.
1287   if (is_ext_connected != m_mplus_state.passthrough_port)
1288   {
1289     m_mplus_state.passthrough_port = is_ext_connected;
1290 
1291     DEBUG_LOG(WIIMOTE, "WiiRemote: M+ passthrough port event: %d.", is_ext_connected);
1292 
1293     // With no passthrough extension we'll be happy with the current mode.
1294     if (!is_ext_connected)
1295       m_mplus_desired_mode = m_mplus_state.current_mode;
1296 
1297     ProcessExtensionEvent(is_ext_connected);
1298   }
1299 
1300   if (mplus_data.is_mp_data)
1301   {
1302     m_mplus_state.ProcessData(mplus_data);
1303     return;
1304   }
1305 
1306   if (!IsMotionPlusInDesiredMode())
1307   {
1308     DEBUG_LOG(WIIMOTE, "WiiRemote: Ignoring unwanted passthrough data.");
1309     return;
1310   }
1311 
1312   std::array<u8, sizeof(WiimoteEmu::Nunchuk::DataFormat)> data;
1313   std::copy_n(ext_data, ext_size, data.begin());
1314 
1315   // Undo bit-hacks of M+ passthrough.
1316   WiimoteEmu::MotionPlus::ReversePassthroughModifications(*m_mplus_state.current_mode, data.data());
1317 
1318   ProcessNormalExtensionData(data.data(), u32(data.size()));
1319 }
1320 
ProcessNormalExtensionData(const u8 * ext_data,u32 ext_size)1321 void Device::ProcessNormalExtensionData(const u8* ext_data, u32 ext_size)
1322 {
1323   if (m_extension_id == ExtensionID::Nunchuk)
1324   {
1325     if (ext_size < sizeof(WiimoteEmu::MotionPlus::DataFormat))
1326       return;
1327 
1328     const WiimoteEmu::Nunchuk::DataFormat nunchuk_data =
1329         Common::BitCastPtr<WiimoteEmu::Nunchuk::DataFormat>(ext_data);
1330 
1331     m_nunchuk_state.ProcessData(nunchuk_data);
1332   }
1333   else if (m_extension_id == ExtensionID::Classic)
1334   {
1335     if (ext_size < sizeof(WiimoteEmu::Classic::DataFormat))
1336       return;
1337 
1338     const WiimoteEmu::Classic::DataFormat cc_data =
1339         Common::BitCastPtr<WiimoteEmu::Classic::DataFormat>(ext_data);
1340 
1341     m_classic_state.ProcessData(cc_data);
1342   }
1343 }
1344 
UpdateRumble()1345 void Device::UpdateRumble()
1346 {
1347   static constexpr auto rumble_period = std::chrono::milliseconds(100);
1348 
1349   const auto on_time = std::chrono::duration_cast<Clock::duration>(rumble_period * m_rumble_level);
1350   const auto off_time = rumble_period - on_time;
1351 
1352   const auto now = Clock::now();
1353 
1354   if (m_rumble && (now < m_last_rumble_change + on_time || !off_time.count()))
1355     return;
1356 
1357   if (!m_rumble && (now < m_last_rumble_change + off_time || !on_time.count()))
1358     return;
1359 
1360   m_last_rumble_change = now;
1361   m_rumble ^= true;
1362 
1363   // Rumble flag will be set within QueueReport.
1364   QueueReport(OutputReportRumble{});
1365 }
1366 
UpdateInput()1367 void Device::UpdateInput()
1368 {
1369   if (!m_wiimote->IsConnected())
1370   {
1371     g_controller_interface.RemoveDevice(
1372         [this](const Core::Device* device) { return device == this; });
1373     return;
1374   }
1375 
1376   UpdateRumble();
1377   RunTasks();
1378 
1379   WiimoteReal::Report report;
1380   while (m_wiimote->GetNextReport(&report))
1381   {
1382     ProcessInputReport(report);
1383     RunTasks();
1384   }
1385 }
1386 
ProcessData(const WiimoteEmu::MotionPlus::DataFormat & data)1387 void Device::MotionPlusState::ProcessData(const WiimoteEmu::MotionPlus::DataFormat& data)
1388 {
1389   // We need the calibration block read to know the sensor orientations.
1390   if (!calibration.has_value())
1391     return;
1392 
1393   gyro_data = data.GetData().GetAngularVelocity(*calibration);
1394 }
1395 
IsWaitingForMotionPlus() const1396 bool Device::IsWaitingForMotionPlus() const
1397 {
1398   return Clock::now() < m_mplus_wait_time;
1399 }
1400 
WaitForMotionPlus()1401 void Device::WaitForMotionPlus()
1402 {
1403   DEBUG_LOG(WIIMOTE, "WiiRemote: Wait for M+.");
1404   m_mplus_wait_time = Clock::now() + std::chrono::seconds{2};
1405 }
1406 
ProcessData(const WiimoteEmu::Nunchuk::DataFormat & data)1407 void Device::NunchukState::ProcessData(const WiimoteEmu::Nunchuk::DataFormat& data)
1408 {
1409   buttons = data.GetButtons();
1410 
1411   // Stick/accel require calibration data.
1412   if (!calibration.has_value())
1413     return;
1414 
1415   stick = data.GetStick().GetNormalizedValue(calibration->stick);
1416   accel = data.GetAccel().GetNormalizedValue(calibration->accel) *
1417           float(MathUtil::GRAVITY_ACCELERATION);
1418 }
1419 
ProcessData(const WiimoteEmu::Classic::DataFormat & data)1420 void Device::ClassicState::ProcessData(const WiimoteEmu::Classic::DataFormat& data)
1421 {
1422   buttons = data.GetButtons();
1423 
1424   // Sticks/triggers require calibration data.
1425   if (!calibration.has_value())
1426     return;
1427 
1428   sticks[0] = data.GetLeftStick().GetNormalizedValue(calibration->left_stick);
1429   sticks[1] = data.GetRightStick().GetNormalizedValue(calibration->right_stick);
1430   triggers[0] = data.GetLeftTrigger().GetNormalizedValue(calibration->left_trigger);
1431   triggers[1] = data.GetRightTrigger().GetNormalizedValue(calibration->right_trigger);
1432 }
1433 
ReadData(AddressSpace space,u8 slave,u16 address,u16 size,std::function<void (ReadResponse)> callback)1434 void Device::ReadData(AddressSpace space, u8 slave, u16 address, u16 size,
1435                       std::function<void(ReadResponse)> callback)
1436 {
1437   OutputReportReadData read_data{};
1438   read_data.space = u8(space);
1439   read_data.slave_address = slave;
1440   read_data.address[0] = u8(address >> 8);
1441   read_data.address[1] = u8(address);
1442   read_data.size[0] = u8(size >> 8);
1443   read_data.size[1] = u8(size);
1444   QueueReport(read_data);
1445 
1446   AddReadDataReplyHandler(space, slave, address, size, {}, std::move(callback));
1447 }
1448 
AddReadDataReplyHandler(AddressSpace space,u8 slave,u16 address,u16 size,std::vector<u8> starting_data,std::function<void (ReadResponse)> callback)1449 void Device::AddReadDataReplyHandler(AddressSpace space, u8 slave, u16 address, u16 size,
1450                                      std::vector<u8> starting_data,
1451                                      std::function<void(ReadResponse)> callback)
1452 {
1453   // Data read may return a busy ack.
1454   auto ack_handler = MakeAckHandler(OutputReportID::ReadData, [callback](ErrorCode result) {
1455     DEBUG_LOG(WIIMOTE, "WiiRemote: Read ack error: %d.", int(result));
1456     callback(ReadResponse{});
1457   });
1458 
1459   // Or more normally a "ReadDataReply".
1460   auto read_handler = [this, space, slave, address, size, data = std::move(starting_data),
1461                        callback =
1462                            std::move(callback)](const InputReportReadDataReply& reply) mutable {
1463     if (Common::swap16(reply.address) != address)
1464       return ReportHandler::HandlerResult::NotHandled;
1465 
1466     if (reply.error != u8(ErrorCode::Success))
1467     {
1468       DEBUG_LOG(WIIMOTE, "WiiRemote: Read reply error: %d.", int(reply.error));
1469       callback(ReadResponse{});
1470 
1471       return ReportHandler::HandlerResult::Handled;
1472     }
1473 
1474     const auto read_count = reply.size_minus_one + 1;
1475 
1476     data.insert(data.end(), reply.data, reply.data + read_count);
1477 
1478     if (read_count < size)
1479     {
1480       // We have more data to acquire.
1481       AddReadDataReplyHandler(space, slave, address + read_count, size - read_count,
1482                               std::move(data), std::move(callback));
1483     }
1484     else
1485     {
1486       // We have all the data.
1487       callback(std::move(data));
1488     }
1489 
1490     return ReportHandler::HandlerResult::Handled;
1491   };
1492 
1493   AddReportHandler(
1494       std::function<ReportHandler::HandlerResult(const InputReportReadDataReply& reply)>(
1495           std::move(read_handler)),
1496       std::move(ack_handler));
1497 }
1498 
1499 template <typename T, typename C>
WriteData(AddressSpace space,u8 slave,u16 address,T && data,C && callback)1500 void Device::WriteData(AddressSpace space, u8 slave, u16 address, T&& data, C&& callback)
1501 {
1502   OutputReportWriteData write_data = {};
1503   write_data.space = u8(space);
1504   write_data.slave_address = slave;
1505   write_data.address[0] = u8(address >> 8);
1506   write_data.address[1] = u8(address);
1507 
1508   static constexpr auto MAX_DATA_SIZE = std::size(write_data.data);
1509   write_data.size = u8(std::min(std::size(data), MAX_DATA_SIZE));
1510 
1511   std::copy_n(std::begin(data), write_data.size, write_data.data);
1512 
1513   // Writes of more than 16 bytes must be split into multiple reports.
1514   if (std::size(data) > MAX_DATA_SIZE)
1515   {
1516     auto next_write = [this, space, slave, address,
1517                        additional_data =
1518                            std::vector<u8>(std::begin(data) + MAX_DATA_SIZE, std::end(data)),
1519                        callback = std::forward<C>(callback)](ErrorCode result) mutable {
1520       if (result != ErrorCode::Success)
1521         callback(result);
1522       else
1523         WriteData(space, slave, address + MAX_DATA_SIZE, additional_data, std::move(callback));
1524     };
1525 
1526     QueueReport(write_data, std::move(next_write));
1527   }
1528   else
1529   {
1530     QueueReport(write_data, std::forward<C>(callback));
1531   }
1532 }
1533 
ReportHandler(Clock::time_point expired_time)1534 Device::ReportHandler::ReportHandler(Clock::time_point expired_time) : m_expired_time(expired_time)
1535 {
1536 }
1537 
1538 template <typename... T>
AddReportHandler(T &&...callbacks)1539 void Device::AddReportHandler(T&&... callbacks)
1540 {
1541   auto& handler = m_report_handlers.emplace_back(Clock::now() + std::chrono::seconds{5});
1542   (handler.AddHandler(std::forward<T>(callbacks)), ...);
1543 }
1544 
1545 template <typename R, typename T>
AddHandler(std::function<R (const T &)> handler)1546 void Device::ReportHandler::AddHandler(std::function<R(const T&)> handler)
1547 {
1548   m_callbacks.emplace_back([handler = std::move(handler)](const WiimoteReal::Report& report) {
1549     if (report[WiimoteReal::REPORT_HID_HEADER_SIZE] != u8(T::REPORT_ID))
1550       return ReportHandler::HandlerResult::NotHandled;
1551 
1552     T data;
1553 
1554     if (report.size() < sizeof(T) + WiimoteReal::REPORT_HID_HEADER_SIZE + 1)
1555     {
1556       // Off-brand "NEW 2in1" Wii Remote likes to shorten read data replies.
1557       WARN_LOG(WIIMOTE, "WiiRemote: Bad report size (%d) for report 0x%x. Zero-filling.",
1558                int(report.size()), int(T::REPORT_ID));
1559 
1560       data = {};
1561       std::memcpy(&data, report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + 1,
1562                   report.size() - WiimoteReal::REPORT_HID_HEADER_SIZE + 1);
1563     }
1564     else
1565     {
1566       data = Common::BitCastPtr<T>(report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + 1);
1567     }
1568 
1569     if constexpr (std::is_same_v<decltype(handler(data)), void>)
1570     {
1571       handler(data);
1572       return ReportHandler::HandlerResult::Handled;
1573     }
1574     else
1575     {
1576       return handler(data);
1577     }
1578   });
1579 }
1580 
TryToHandleReport(const WiimoteReal::Report & report)1581 auto Device::ReportHandler::TryToHandleReport(const WiimoteReal::Report& report) -> HandlerResult
1582 {
1583   for (auto& callback : m_callbacks)
1584   {
1585     if (const auto result = callback(report); result != HandlerResult::NotHandled)
1586       return result;
1587   }
1588 
1589   return HandlerResult::NotHandled;
1590 }
1591 
IsExpired() const1592 bool Device::ReportHandler::IsExpired() const
1593 {
1594   return Clock::now() >= m_expired_time;
1595 }
1596 
MakeAckHandler(OutputReportID report_id,std::function<void (WiimoteCommon::ErrorCode)> callback)1597 auto Device::MakeAckHandler(OutputReportID report_id,
1598                             std::function<void(WiimoteCommon::ErrorCode)> callback)
1599     -> AckReportHandler
1600 {
1601   return [report_id, callback = std::move(callback)](const InputReportAck& reply) {
1602     if (reply.rpt_id != report_id)
1603       return ReportHandler::HandlerResult::NotHandled;
1604 
1605     callback(reply.error_code);
1606     return ReportHandler::HandlerResult::Handled;
1607   };
1608 }
1609 
IsPerformingTask() const1610 bool Device::IsPerformingTask() const
1611 {
1612   return !m_report_handlers.empty();
1613 }
1614 
ProcessStatusReport(const InputReportStatus & status)1615 void Device::ProcessStatusReport(const InputReportStatus& status)
1616 {
1617   // Update status periodically to keep battery level value up to date.
1618   m_status_outdated_time = Clock::now() + std::chrono::seconds(10);
1619 
1620   m_battery = status.GetEstimatedCharge() * BATTERY_INPUT_MAX_VALUE;
1621   m_leds = status.leds;
1622 
1623   if (!status.ir)
1624     m_ir_state = {};
1625 
1626   const bool is_ext_connected = status.extension;
1627 
1628   // Handle extension port state change.
1629   if (is_ext_connected != m_extension_port)
1630   {
1631     DEBUG_LOG(WIIMOTE, "WiiRemote: Extension port event: %d.", is_ext_connected);
1632 
1633     m_extension_port = is_ext_connected;
1634 
1635     // Data reporting stops on an extension port event.
1636     m_reporting_mode = InputReportID::ReportDisabled;
1637 
1638     ProcessExtensionEvent(is_ext_connected);
1639 
1640     // The M+ is now in an unknown state.
1641     m_mplus_state = {};
1642 
1643     if (is_ext_connected)
1644     {
1645       // We can assume the M+ is settled on an attachment event.
1646       m_mplus_wait_time = Clock::now();
1647     }
1648     else
1649     {
1650       // "Nunchuk" will be the most used mode and also works with no passthrough extension.
1651       m_mplus_desired_mode = MotionPlusState::PassthroughMode::Nunchuk;
1652 
1653       // If an extension is not connected the M+ is either disabled or resetting.
1654       m_mplus_state.current_mode = MotionPlusState::PassthroughMode{};
1655     }
1656   }
1657 }
1658 
1659 }  // namespace ciface::Wiimote
1660