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