├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── doc ├── DESIGN.md ├── arduino-main-usb-iscp-pinout.png └── arduino-main-usb-iscp-pinout.xcf ├── src ├── bdsp │ ├── README.md │ └── bdsp.c ├── lib │ ├── automation-utils.c │ ├── automation-utils.h │ ├── automation.c │ ├── automation.h │ ├── persist.c │ ├── persist.h │ ├── user-io.c │ └── user-io.h ├── swsh │ ├── README.md │ └── swsh.c └── usb-iface │ ├── LUFAConfig.h │ ├── Makefile │ ├── common.h │ ├── standalone-usb-iface.c │ ├── usb-descriptors.c │ ├── usb-descriptors.h │ └── usb-iface.c └── tools └── read_reset_count.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{c,h}] 8 | charset = utf-8 9 | indent_style = tab 10 | indent_size = 4 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.hex 3 | *.elf 4 | *.eep 5 | *.o 6 | *.map 7 | src/usb-iface/obj -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lufa"] 2 | path = lufa 3 | url = https://github.com/abcminiuser/lufa.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vincent Duvert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-Wall -Wextra -Werror=overflow -Werror=type-limits -std=c11 -Os -I src/usb-iface -I src/lib 2 | PROGRAMMER=avrispmkii 3 | 4 | # Optionally add .hex here so it is built when make is invoked 5 | # without arguments. 6 | all: swsh.hex bdsp.hex usb-iface.hex 7 | @echo "Build done. Use flash- to flash a file." 8 | 9 | # Put program definitions (.o => src/.elf) here 10 | # make .hex will generate the final program and make flash- will 11 | # flash it. 12 | src/swsh.elf: src/swsh/swsh.o src/lib/automation.o src/lib/automation-utils.o src/lib/user-io.o 13 | src/bdsp.elf: src/bdsp/bdsp.o src/lib/persist.o src/lib/automation.o src/lib/automation-utils.o src/lib/user-io.o 14 | 15 | flash-%: %.hex 16 | avrdude -p atmega328p -c $(PROGRAMMER) -P usb -U flash:w:$<:i 17 | 18 | flash-usb-iface: usb-iface.hex 19 | avrdude -p m16u2 -c $(PROGRAMMER) -P usb -U flash:w:$< -U lfuse:w:0xFF:m -U hfuse:w:0xD9:m -U efuse:w:0xF4:m -U lock:w:0x0F:m 20 | 21 | restore-usb-iface: UNO-dfu_and_usbserial_combined.hex 22 | avrdude -p m16u2 -c $(PROGRAMMER) -P usb -U flash:w:$< -U lfuse:w:0xFF:m -U hfuse:w:0xD9:m -U efuse:w:0xF4:m -U lock:w:0x0F:m 23 | 24 | usb-iface.hex: lufa/.git src/usb-iface/usb-iface.c src/usb-iface/standalone-usb-iface.c src/usb-iface/usb-descriptors.c 25 | $(MAKE) -C src/usb-iface usb-iface.hex 26 | cp src/usb-iface/usb-iface.hex usb-iface.hex 27 | 28 | UNO-dfu_and_usbserial_combined.hex: 29 | curl -O https://raw.githubusercontent.com/arduino/ArduinoCore-avr/master/firmwares/atmegaxxu2/UNO-dfu_and_usbserial_combined.hex 30 | 31 | %.hex: src/%.elf 32 | avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 $< $<.eep 33 | avr-objcopy -O ihex -R .eeprom $< $@ 34 | 35 | src/%.elf: 36 | @[ "-n" "$^" ] || (echo "No source files specified to build $@; add a program definition at the top of the Makefile." >&2; exit 1) 37 | avr-gcc -mmcu=atmega328p -flto -fuse-linker-plugin -Wl,--gc-sections -o $@ $^ 38 | 39 | %.o: %.c 40 | avr-gcc $(CFLAGS) -mmcu=atmega328p -DF_CPU=16000000 -ffunction-sections -fdata-sections -flto -fuse-linker-plugin -o $@ -c $< 41 | 42 | clean: 43 | rm -f *.hex src/*.o src/*.elf src/*.eep src/*/*.o src/*/*.elf src/*/*.eep 44 | make -C src/usb-iface clean 45 | 46 | lufa/.git: .gitmodules 47 | @echo "- Initializing/Updating LUFA submodule" 48 | git submodule update --init 49 | 50 | # Disable automatic removal of intermediary files 51 | .SECONDARY: 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino UNO R3 Switch controller emulator 2 | ========================================= 3 | 4 | This project provides an easy-to use programming API to emulate a Nintendo 5 | Switch controller using an Arduino UNO R3. 6 | 7 | The API allows sending button presses/stick movements on the emulated 8 | controller, to read the state of a physical button connected between the 9 | Arduino’s pins 12 and ground, and to control the Arduino’s on-board LED as well 10 | as an external buzzer. 11 | 12 | Pokémon Sword/Shield automation 13 | ------------------------------- 14 | 15 | A sample program is provided to automate some tasks in Pokémon Sword/Shield. 16 | Details about this can be found in [this file](src/swsh/README.md). 17 | 18 | Pokémon Brilliant Diamond/Shining Pearl automation 19 | -------------------------------------------------- 20 | 21 | A sample program is provided to automate some tasks in Pokémon BD/SP. 22 | Details about this can be found in [this file](src/bdsp/README.md). 23 | 24 | How does it work? 25 | ----------------- 26 | 27 | The Arduino UNO R3 board has a ATmega328P microcontroller. User code programmed 28 | using the Arduino IDE runs on this microcontroller, which controls most I/O 29 | pins on the board. 30 | 31 | There is, however, another microcontroller on the board (the small square chip 32 | between the USB port and the RX/TX LEDs). This is a ATmega16U2, and it controls 33 | the USB interface. 34 | 35 | By default, the 16U2 simulates a USB-to-serial interface. Serial port access 36 | from the 328P (using for instance the Arduino `Serial` library) are routed 37 | through the 16U2 before getting to the attached computer. 38 | 39 | Using an external ISCP programmer, it is possible to reprogram the 16U2 in 40 | order to make it simulate a USB Nintendo Switch controller and send inputs 41 | directly. This is the approach used in [[1]]; with this configuration, the 328P 42 | is not used at all and can even be removed from the board. The main drawback, 43 | however, is that the 16U2 has limited RAM and Flash space, and cannot access 44 | most I/O ports on the Arduino board. 45 | 46 | A better approach is to use both microcontrollers: 47 | - The ATmega328P reads inputs from the user, display current status, decides 48 | which inputs to simulates, and send the calculated inputs to the ATmega16U2. 49 | - The ATmega16U2 receives commands from the ATmega328P and forwards them to 50 | the USB interface as data. 51 | 52 | This also allows asynchronous handling: the 16U2 can continue to process 53 | USB requests from the attached computer/Switch while the 328P is blocked in a 54 | delay loop or waiting for user input. 55 | 56 | The 328P also does not need to include a library to deal with USB requests, 57 | since it only needs to be able to send commands to the 16U2; this frees up 58 | space for more application code. 59 | 60 | An example of this approach (for a generic USB controller) can be found 61 | on [[2]] (Internet Archive link). 62 | 63 | See [the DESIGN file](doc/DESIGN.md) for more implementation details. 64 | 65 | Required hardware 66 | ----------------- 67 | 68 | - An Arduino UNO R3 69 | - A push button plugged between pins 12 and GND on the top connector. Most 70 | push buttons should be able to be plugged directly on the Arduino board, 71 | without extra circuitry. 72 | - An external ICSP programmer, like the AVRISP mkII. It may be possible to 73 | reprogram the Arduino using DFU mode (according to [[2]]) but this has not 74 | be tested. (Note: DFU programming may also stop working once this program is 75 | flashed onto the Arduino, so you should not attempt it if you do not have 76 | access to an external programmer) 77 | - A buzzer can be optionally attached between pins 2 and GND of the Arduino 78 | board, in order for the automation process to notify the user when something 79 | needs their attention. 80 | 81 | Required software 82 | ----------------- 83 | 84 | An AVR toolchain providing `avr-gcc`, `avr-objcopy` and `avrdude` in the 85 | `PATH`. 86 | 87 | - On macOS, you can install `avr-gcc avr-binutils avr-libc avrdude` from 88 | [MacPorts](http://macports.org/). Homebrew might also be used. 89 | - On Debian Linux and similar distributions, you can install 90 | `gcc-avr avr-libc binutils-avr avrdude`. 91 | 92 | LUFA [[3]] is used for the USB interface handling; it is included in this 93 | repository as a submodule and will be automatically retrieved if needed. 94 | 95 | Building 96 | -------- 97 | 98 | Running `make` will produce the following files: 99 | - `usb-iface.hex` is the program for the ATmega16U2 managing the USB 100 | interface. 101 | - `swsh.hex` is the Pokémon Sword/Shield automation program, running on the 102 | ATmega328P. You can create your own automation program and edit the 103 | `Makefile` to build it. 104 | 105 | Programming 106 | ----------- 107 | 108 | The Arduino UNO R3 has two headers for programming the USB interface controller 109 | and the main microcontroller, respectively. Their pinouts are indicated in 110 | the following picture: 111 | 112 | ![The ISCP header for programming the Arduino UNO R3 USB interface is on the 113 | top left of the board, next to the USB port. Its top row of pins are RST, 114 | SCK, MISO; its bottom row of pins are GND, MOSI, VCC. The ISCP header for 115 | programming the main microcontroller is on the center right of the board. 116 | Its top row of pins are MISO, VCC; its middle row of pins are SCK, MOSI; 117 | its bottom row of pins are RST, GND](doc/arduino-main-usb-iscp-pinout.png) 118 | 119 | Start by connecting the programmer to the ISCP header for the USB interface. 120 | (Some programmers’ connectors have a notch on the MISO/SCK/RST side that will 121 | bump into the top I/O header, making it a tight fit). Flash the `usb-iface.hex` 122 | file, either manually or by running `make flash-usb-iface`. 123 | 124 | Unplug the programmer and connect it to the main microcontroller’s ISCP header. 125 | Flash the `swsh.hex` file, either manually or by running `make flash-swsh`. 126 | 127 | Use any programmer supported by avrdude, `avrdude -c ?`, by specifying 128 | `PROGRAMMER` when flashing. E.g. `make PROGRAMMER=usbtiny flash-swsh`. 129 | 130 | Factory restore 131 | --------------- 132 | 133 | To restore the original Arduino USB interface and bootloader, follow these 134 | steps: 135 | 136 | - Connect the programmer to the ISCP header for the USB interface and flash 137 | the `UNO-dfu_and_usbserial_combined.hex` file that can be found on [[4]]. 138 | This can be done automatically by running `make restore-usb-iface`. 139 | 140 | - Connect the programm to the ISCP header for the main microcontroller, and 141 | start the Arduino IDE. Make sure that the correct programmer is selected in 142 | the `Tools>Programmer` and select `Tools>Burn bootloader`. 143 | 144 | The Arduino should then be programmable using its regular USB interface. 145 | 146 | [1]: https://github.com/Bowarcky/pkmn-swsh-automation-tools 147 | [2]: http://web.archive.org/web/20150802033750/http://hunt.net.nz/users/darran/ 148 | [3]: https://github.com/abcminiuser/lufa 149 | [4]: http://github.com/arduino/ArduinoCore-avr/blob/master/firmwares/atmegaxxu2 150 | -------------------------------------------------------------------------------- /doc/DESIGN.md: -------------------------------------------------------------------------------- 1 | Inter-Processor Communication 2 | ============================= 3 | 4 | This file documents how the communication between the main Arduino 5 | microcontroller (ATmega328P, called “main µC” in this document) and the USB 6 | interface microcontroller (ATmega16U2, called “USB µC”) is implemented. 7 | 8 | Controller data to the Switch 9 | ----------------------------- 10 | 11 | The controller is polled by the Switch at a constant rate of 125 Hz. This means 12 | that 125 times a second, the USB µC needs to provide the current state of the 13 | controller (pressed buttons, stick orientation, etc). This is sent in an 14 | USB report message whose size is 8 bytes: 15 | - The two first bytes are the state of the buttons (A, B, …) as a bitfield. 16 | The controller emulated is the Pokken Tournament controller which has no 17 | Home and Capture buttons, but setting the appropriate bits in the bitfield 18 | is sufficient to make them recognized by the Switch. 19 | - The next byte is the status of the d-pad: each direction (top, top-left, 20 | left, …) is assigned a value, so there are 9 possible values. 21 | - The next two bytes are the state of the L Stick, the first is the X 22 | coordinate (0: leftmost, 128: center, 255: rightmost), the second is the 23 | Y coordinate (0: topmost, 128: center, 255: bottommost). 24 | - The next two bytes are the state of the R Stick. 25 | - The next byte is vendor specific; its purpose is unknown. Putting a 0 value 26 | seems to work correctly. 27 | 28 | Even though the controller is polled 125 times a second, it does not mean that 29 | games can react that quickly to input. Empirical testing on the Switch’s main 30 | menu seems to indicate that pressing and releasing the left d-pad 5 times, 31 | then pressing and releasing the right d-pad 5 times, does not reliably moves 32 | the cursor back and forth unless the presses and releases last for 5 USB 33 | report messages. 34 | 35 | Thus, the USB µC will be programmed to repeat the same controller state five 36 | times when queried by the Switch. This means that the effective state can 37 | be changed 125 / 5 = 25 times a second. This could probably be easily changed 38 | in the future if more frequent updates are needed. 39 | 40 | Note that different systems may poll the controller at different rates; it is 41 | possible for the automated actions to happen at a faster rate if the emulated 42 | controller is plugged to a PC, for instance. 43 | 44 | Processor synchronization 45 | ------------------------- 46 | 47 | Every five controller reports (a “cycle”), the USB µC will update the data 48 | it sends to the host. This means that the main µC needs to send updates at the 49 | correct rate; it should also be able to do some computation between the 50 | updates. 51 | 52 | To achieve this, the USB µC receives in advance the data update for the next 53 | cycle, and store it in a buffer. When the next cycle starts, it empties the 54 | buffer and signals the main µC that it can start sending the next update. 55 | 56 | With this design, the main µC execution is ahead of the USB µC execution by 57 | at most one cycle. 58 | 59 | Communication 60 | ------------- 61 | 62 | The main µC and the USB µC are connected with a serial link and have embedded 63 | serial controllers. That makes it relatively straightforward to send and 64 | receive data between them. 65 | 66 | The communication is bidirectional; the USB µC will indicate that it is ready 67 | to accept data from the main µC by writing a byte on the serial line; the main 68 | µC will then send a full data update on the link, and wait for the next “data 69 | accept” byte. 70 | 71 | To simplify the design, a busy-loop will be used for sending or receiving data, 72 | instead of using interrupts: 73 | - On the USB µC side, a busy-loop is already used for USB handling, so 74 | processing incoming serial data can be done as part of the loop. 75 | - The main µC only checks for incoming serial data from time to time (when it 76 | verifies that the USB µC is ready for more data. 77 | 78 | Exchanged data 79 | -------------- 80 | 81 | The USB µC sends a single character to the main µC to signal that it is ready 82 | to accept the next data update. This character is `'R'`. 83 | 84 | In response, the main µC sends a string of 8 bytes to the USB µC. The first 85 | 7 bytes are the controller data (the eight byte of the controller data is not 86 | sent as it is hard-coded as 0 in the USB µC code). The eight byte serves as 87 | an end-of-data marker, to validate that no data was lost. It it also used to 88 | change the state of the RX/TX LEDs on the Arduino board (these LEDs are 89 | controlled by the USB µC). The valid values are: 90 | - `0xAC`: Both LEDs off 91 | - `0xAD`: TX LED on 92 | - `0xAE`: RX LED on 93 | - `0xAF`: Both LEDs on 94 | 95 | Sequence of operations 96 | ---------------------- 97 | 98 | On the USB µC side, a 8-byte receive buffer is allocated to hold the data 99 | received from the main µC. This buffer is initially filled with “neutral” 100 | controller data (all buttons unpressed, sticks centered) and both LEDs off. 101 | 102 | If a byte of data is available on the serial interface, it is added to the 103 | receive buffer. If the buffer is full when this happens, a data error is 104 | detected by the serial controller, or the last received byte is not what is 105 | expected, the USB µC will enter “panic mode” (see below). 106 | 107 | Another buffer, called the output buffer, contains the data that is actually 108 | emitted when a poll request is received from the host (Switch or computer). 109 | 110 | Every cycle, the USB µC checks if the receive buffer is full. If it is, the 111 | receive buffer data is put into the output buffer and emptied, the state of 112 | the LEDs is updated, and the “ready for more data” signal is sent to the main 113 | µC. 114 | 115 | The main µC is supposed to have provided a full data update at each cycle; this 116 | ensures that the timings are predictable. That means at the start of a cycle, 117 | if the USB µC receive buffer is not full, it will enter “panic mode”. 118 | 119 | There is an exception to this rule, however; if the receive buffer is 120 | completely empty at the start of a cycle and the output buffer contains neutral 121 | controller data, the USB µC will not panic, and wait for the next cycle. 122 | 123 | This allows the main µC to pause sending controller updates for as long as it 124 | wants (to wait for user input, for instance), as long as it first sets the 125 | controller to a neutral state. 126 | 127 | The main µC uses a 8-byte transmit buffer, that is initially set to “neutral” 128 | controller data and both LEDs off. 129 | 130 | API calls like “set buttons” modify values in this transmit buffer, then send 131 | it to the USB µC. The transmission is done as follows: 132 | 133 | 1. Wait for a byte to be received on the serial interface 134 | 2. Read the received byte 135 | 3. Write the transmit buffer on the serial interface 136 | 137 | Resync/start-up detect 138 | ---------------------- 139 | 140 | It can be useful to be able to reprogram the main µC while the Arduino is 141 | connected to the Switch (or another computer), for easier automation debugging. 142 | 143 | Since the USB µC is unaffected by the main µC being reprogrammed, it can 144 | continue processing USB requests from the host in a completely transparent 145 | manner. 146 | 147 | However, once the main µC restarts after programming (or after the Arduino’s 148 | reset button is pressed), the two µC are in a desynchronized state: 149 | - If the main µC is interrupted while sending data to the USB µC, the USB µC 150 | will wait for data indefinitely and will not send a “ready for data” signal. 151 | - If the main µC is interrupted after a “ready for data” signal is sent by 152 | the USB µC but before it is taken into account, the signal will be lost 153 | since the serial controller will get reset. 154 | 155 | This means that a special protocol is required to properly re-sync the µCs in 156 | that situation. It is implemented in the following manner: 157 | 158 | - At startup, the USB µC waits 1 ms and sends a `'I'` character to the main 159 | µC. 160 | - At startup, the main µC waits to receive a `'I'` for 2 ms. If it receives 161 | it, it continues processing. 162 | - If that is not the case, it enters a loop: 163 | - Send a zero byte to the USB µC 164 | - Wait up to 5 ms to receive a `'S'` character. If not received, repeat. 165 | - This means that on reset from the main µC, whatever the USB µC’s state is, 166 | it will eventually receive a zero byte and store it at the end of its 167 | receive buffer (where the end-of-data marker should be). It will detect this 168 | situation and send a `'S'` character. to the main µC. 169 | 170 | This means that the USB µC will either receive a `'I'` if it is starting at the 171 | same time at the USB µC is, or a `'S'` if it was reprogramed/reset; in both 172 | cases, it will receive a `'R'` character later when the USB µC is ready to 173 | accept new data. 174 | 175 | This start-up detection is useful for user code, since it allows performing 176 | some actions only on power-up and not on main µC reset. For instance, the 177 | Switch controller set-up process (press L/R, press A, press Home then A to 178 | return to game) can be skipped when the main µC restart after being 179 | reprogrammed. 180 | 181 | Panic mode 182 | ---------- 183 | 184 | Both µCs can enter panic mode if they detect an abnormal situation. When in 185 | panic mode, the LEDs (RX and TX for the USB µC, L for the main µC) will blink 186 | a certain number of times depending on the type of error, wait for a little 187 | while, then repeat. 188 | 189 | When in panic mode, the main µC halts all processing (the panic function never 190 | returns), but the USB µC still communicates with the host (it only sends 191 | neutral controller data, though). 192 | -------------------------------------------------------------------------------- /doc/arduino-main-usb-iscp-pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinDuv/switch-arduino-controller/b53a5a626796b79de7fb2a324e5bb48edaeec744/doc/arduino-main-usb-iscp-pinout.png -------------------------------------------------------------------------------- /doc/arduino-main-usb-iscp-pinout.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinDuv/switch-arduino-controller/b53a5a626796b79de7fb2a324e5bb48edaeec744/doc/arduino-main-usb-iscp-pinout.xcf -------------------------------------------------------------------------------- /src/bdsp/README.md: -------------------------------------------------------------------------------- 1 | Pokémon Brilliant Diamond/Shining Pearl automation 2 | ================================================== 3 | 4 | This module automates some tasks in Pokémon Brilliant Diamond and Shining Pearl. 5 | 6 | Requirements 7 | ------------ 8 | 9 | You will need an Arduino UNO R3, an external Arduino programmer, and a 10 | pushbutton inserted in the Arduino board, between pins 13 and GND (on the top 11 | row). 12 | 13 | You can additionally install a buzzer between pins 2 and GND. 14 | 15 | See [the main README](../../README.md#required-hardware) for details. 16 | 17 | Installation 18 | ------------ 19 | 20 | Use `make` to build the `usb-iface.hex` and `bdsp.hex` files. Flash 21 | `usb-iface.hex` to the USB interface microcontroller (ATmega16U2), and 22 | `bdsp.hex` to the main microcontroller (ATmega328P). 23 | 24 | See the main README for the 25 | [required software](../../README.md#required-software), the 26 | [build procedure](../../README.md#building), and the 27 | [programming procedure](../../README.md#programming). 28 | 29 | Reset count 30 | ----------- 31 | 32 | The legendary shiny hunting features count the number of time a reset is 33 | performed. The reset count is stored in the Arduino’s non-volatile memory 34 | (EEPROM) so it is kept even when the device is disconnected. 35 | 36 | The reset count can be displayed on the Switch screen, or set to zero, using the 37 | features described below. 38 | 39 | You can also read the refresh count from a computer using the programmer and the 40 | `tools/read_reset_count.py` script. (Requires Python 3) 41 | 42 | **Note**: Re-flashing the main microcontroller will erase the EEPROM and lose 43 | the reset count. I have not yet found a way to preserve the EEPROM contents or 44 | to restore it after it is lost (`avrdude` can read the EEPROM, but writing to it 45 | seems to do nothing) 46 | 47 | Usage 48 | ----- 49 | 50 | Plug the Arduino to the Switch; the L LED on the Arduino board should start 51 | blinking rapidly, and the TX/RX LEDs should be off. 52 | 53 | To start the automation process, start Pokémon BD/SP (if it is not already), and 54 | put the game in the required state (which depends on the task to be automated; 55 | see below for details). 56 | 57 | Press Home to get to the Switch main menu (the selection should be on 58 | the game icon) and press the pushbutton on the Arduino board. The emulated 59 | controller will get auto-registered as controller 2, and then will access the 60 | controller settings to register as controller 1. It will then get back to the 61 | game, ready to control it. 62 | 63 | Once it’s done, the Arduino L LED will blink once per second, and both the 64 | RX and TX LEDs will be lit up. You are in the “main menu”, which allows you 65 | to select which automation feature to perform. Press the pushbutton on the 66 | board once to activate feature 1; twice to activate feature 2; etc. 67 | 68 | The different automation features are described below. 69 | 70 | ### Temporary control [Feature 1 — one button press] 71 | 72 | **Pre-requisites:** None 73 | 74 | Pokémon BD/SP only allows one controller to be enabled while the game is 75 | running; this features allows you to regain control of the game with your 76 | controller. 77 | 78 | When the feature is activated, the virtual controller will get back to the 79 | main menu and open the controller management menu, allowing you to press A on 80 | your controller to reconnect it. Once that’s done, you can close the menu 81 | and get back to the game. The Arduino L LED will blink rapidly. 82 | 83 | Once you’re ready to give back control to the virtual controller, press Home 84 | to get back to the main menu Switch main menu (the selection should be on 85 | the game icon) and press the pushbutton again. You will be back to the 86 | game, ready to activate another feature. 87 | 88 | While this feature is running, both TX and RX LEDs are off. 89 | 90 | ### Show reset count [Feature 2 - two button presses] 91 | 92 | **Pre-requisites:** No menus open 93 | 94 | This feature displays the [reset count](#reset-count) on the screen. 95 | 96 | When it is activated, it will open the Mystery Gift by code function, then type 97 | the number of resets on the keyboard so it is displayed. It will then wait for 98 | a button press, then cancel out of the menus. 99 | 100 | While this feature is running, the TX LED is on, the RX LED is off. 101 | 102 | ### Set reset count to zero [Feature 3 - three button presses] 103 | 104 | **Pre-requisites:** None 105 | 106 | This feature sets the [reset count](#reset-count) to zero. 107 | 108 | When it is activated, the TX LED will light up and the L LED will blink. Press 109 | the button once to perform the reset, or press it rapidly twice to cancel. In 110 | both case, automation will return to the main menu. 111 | 112 | While this feature is running, the RX LED is on, the TX LED is off. 113 | 114 | ### Shiny Arceus hunting [Feature 4 - four button presses] 115 | 116 | **Pre-requisites:** 117 | 118 | You will need the Azure Flute (available in your room after obtaining the 119 | National Pokédex *and* completing Pokémon Legends: Arceus on the same console). 120 | 121 | Go to the Spear Pillar room, go forward a bit and the staircase leading to 122 | Arceus will appear. 123 | 124 | Go to the top of the stairs, right before the platform, facing forward. 125 | **Save**. 126 | 127 | When activated, this feature will repetitively reset the game, and go forward 128 | to trigger the encounter with Arceus. If it is not shiny, you can press the 129 | button once to reset the game and try again. 130 | 131 | You can press the button twice to get into temporary control, then go back to 132 | the main menu. (This is not recommended to use when a shiny is found — it is 133 | safer to unplug the Arduino in that case; the [reset count](#reset-count) is 134 | preserved anyway) 135 | 136 | While this feature is running, both TX and RX LEDs are on. 137 | 138 | -------------------------------------------------------------------------------- /src/bdsp/bdsp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Pokémon Brilliant Diamond/Shining Pearl automation 3 | */ 4 | 5 | #include 6 | 7 | #include "automation-utils.h" 8 | #include "user-io.h" 9 | #include "persist.h" 10 | 11 | /* Static functions */ 12 | static void temporary_control(void); 13 | static void display_reset_count(void); 14 | static void zero_reset_count(void); 15 | static void shiny_arceus_hunting(void); 16 | static void reset_game(void); 17 | static uint32_t get_reset_count(void); 18 | 19 | 20 | int main(void) 21 | { 22 | init_automation(); 23 | init_led_button(); 24 | init_persist(); 25 | 26 | /* Initial beep to confirm that the buzzer works */ 27 | beep(); 28 | 29 | /* Wait for the user to press the button (should be on the Switch main menu) */ 30 | count_button_presses(100, 100); 31 | 32 | /* Set the virtual controller as controller 1 */ 33 | switch_controller(REAL_TO_VIRT); 34 | 35 | for (;;) { 36 | /* Set the LEDs, and make sure automation is paused while in the 37 | menu */ 38 | set_leds(NO_LEDS); 39 | pause_automation(); 40 | 41 | /* Feature selection menu */ 42 | uint8_t count = count_button_presses(100, 900); 43 | 44 | for (uint8_t i = 0 ; i < count ; i += 1) { 45 | beep(); 46 | _delay_ms(100); 47 | } 48 | 49 | switch (count) { 50 | case 1: 51 | temporary_control(); 52 | break; 53 | 54 | case 2: 55 | display_reset_count(); 56 | break; 57 | 58 | case 3: 59 | zero_reset_count(); 60 | break; 61 | 62 | case 4: 63 | shiny_arceus_hunting(); 64 | break; 65 | 66 | default: 67 | /* Wrong selection */ 68 | delay(100, 200, 1500); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | 75 | /* 76 | * Temporary gives back control to the user by performing controller switch. 77 | */ 78 | void temporary_control(void) 79 | { 80 | set_leds(NO_LEDS); 81 | 82 | /* Allow the user to connect their controller back as controller 1 */ 83 | switch_controller(VIRT_TO_REAL); 84 | 85 | /* Wait for the user to press the button (should be on the Switch main menu) */ 86 | count_button_presses(100, 100); 87 | 88 | /* Set the virtual controller as controller 1 */ 89 | switch_controller(REAL_TO_VIRT); 90 | } 91 | 92 | 93 | /* 94 | * Shows the number of resets on the screen by using the Mystery Gift Code 95 | * on-screen keyboard. 96 | */ 97 | void display_reset_count(void) 98 | { 99 | set_leds(TX_LED); 100 | 101 | SEND_BUTTON_SEQUENCE( 102 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Display menu */ 103 | { BT_NONE, DP_RIGHT, SEQ_HOLD, 30 }, /* Go right */ 104 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 20 }, /* Go down */ 105 | /* Cursor now on Mystery gift */ 106 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter Mystery gift */ 107 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 50 }, /* Wait for menu */ 108 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 1 }, /* Move to Via Code */ 109 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Confirm */ 110 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for warning */ 111 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Confirm warning */ 112 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release all */ 113 | ); 114 | 115 | /* Wait for Internet connection */ 116 | _delay_ms(5000); 117 | 118 | /* If this got the game connected to the internet, there is an additional 119 | dialog that can be exited with A or B before the keyboard shows up. If 120 | the game was already connected, the keyboard is displayed directly; in 121 | that situation, pressing A will type a 1 and pressing B will close the 122 | keyboard. */ 123 | SEND_BUTTON_SEQUENCE( 124 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Confirm “Connected” ? */ 125 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for keyboard */ 126 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Type a 1 */ 127 | /* The keyboard is now displayed, with either “1” or “11” depending on 128 | if the game was already connected to the Internet or not. */ 129 | { BT_B, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Clear keyboard input */ 130 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release all */ 131 | ); 132 | 133 | /* Write the number */ 134 | type_number_on_keyboard(get_reset_count()); 135 | 136 | /* Wait for user to press the button */ 137 | beep(); 138 | count_button_presses(100, 100); 139 | 140 | /* Exit menus */ 141 | SEND_BUTTON_SEQUENCE( 142 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Exit keyboard */ 143 | { BT_B, DP_NEUTRAL, SEQ_MASH, 50 }, /* Exit all menus */ 144 | ); 145 | } 146 | 147 | 148 | /* 149 | * Sets the reset count to zero, after user confirmation 150 | */ 151 | void zero_reset_count(void) 152 | { 153 | set_leds(RX_LED); 154 | pause_automation(); 155 | 156 | beep(); 157 | beep(); 158 | if (count_button_presses(200, 200) == 1) { 159 | persist_set_value(0); 160 | } 161 | } 162 | 163 | /* 164 | * Repeat Arceus encounters with game resets 165 | */ 166 | void shiny_arceus_hunting(void) 167 | { 168 | set_leds(BOTH_LEDS); 169 | 170 | for (;;) { 171 | /* Stick up then neutral */ 172 | send_update(BT_NONE, DP_NEUTRAL, S_TOP, S_NEUTRAL); 173 | send_update(BT_NONE, DP_NEUTRAL, S_TOP, S_NEUTRAL); 174 | send_update(BT_NONE, DP_NEUTRAL, S_TOP, S_NEUTRAL); 175 | send_update(BT_NONE, DP_NEUTRAL, S_TOP, S_NEUTRAL); 176 | send_update(BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 177 | 178 | /* Wait for the animation to finish */ 179 | _delay_ms(12000); 180 | 181 | SEND_BUTTON_SEQUENCE( 182 | { BT_A, DP_NEUTRAL, SEQ_MASH, 20 }, /* Mash A */ 183 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 125 }, /* Wait a bit */ 184 | ); 185 | 186 | beep(); 187 | if (count_button_presses(100, 100) > 1) { 188 | temporary_control(); 189 | return; 190 | } 191 | 192 | reset_game(); 193 | } 194 | } 195 | 196 | 197 | /* 198 | * Restarts the game. 199 | * The reset counter is incremented. 200 | * When this returns, the game should be accepting inputs. 201 | */ 202 | void reset_game(void) 203 | { 204 | persist_set_value(get_reset_count() + 1); 205 | 206 | SEND_BUTTON_SEQUENCE( 207 | { BT_H, DP_NEUTRAL, SEQ_HOLD, 3 }, /* Home button */ 208 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for home */ 209 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Ask to close game */ 210 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Wait for menu */ 211 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Confirm close */ 212 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 40 }, /* Wait for close */ 213 | { BT_A, DP_NEUTRAL, SEQ_MASH, 20 }, /* Relaunch game */ 214 | ); 215 | 216 | /* Wait for the game to start */ 217 | _delay_ms(20000); 218 | 219 | SEND_BUTTON_SEQUENCE( 220 | { BT_A, DP_NEUTRAL, SEQ_MASH, 80 }, /* Validate menu */ 221 | ); 222 | 223 | /* Wait for the game to load */ 224 | _delay_ms(9500); 225 | } 226 | 227 | 228 | /* 229 | * Get the reset count (capped at 100000 to detect uninitialized storage) 230 | */ 231 | uint32_t get_reset_count(void) 232 | { 233 | uint32_t value = persist_get_value(); 234 | 235 | if (value > 100000) { 236 | return 0; 237 | } 238 | 239 | return value; 240 | } 241 | -------------------------------------------------------------------------------- /src/lib/automation-utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automation functions that are not tied to a specific Switch game. 3 | */ 4 | 5 | #include "automation-utils.h" 6 | 7 | 8 | /* Perform controller switching */ 9 | void switch_controller(enum switch_mode mode) 10 | { 11 | if (mode == REAL_TO_VIRT) { 12 | SEND_BUTTON_SEQUENCE( 13 | { BT_L, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Reconnect the controller */ 14 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Wait for reconnection */ 15 | ); 16 | } else { 17 | go_to_main_menu(); 18 | } 19 | 20 | /* In both cases, the controller is now connected, the main menu is shown, and the 21 | cursor is on the game icon */ 22 | 23 | SEND_BUTTON_SEQUENCE( 24 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 1 }, /* Switch Online button or News button (< v11) */ 25 | { BT_NONE, DP_RIGHT, SEQ_MASH, 6 }, /* Sleep button */ 26 | { BT_NONE, DP_LEFT, SEQ_MASH, 2 }, /* Controllers button */ 27 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter controllers settings */ 28 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Wait for settings */ 29 | 30 | /* Enter change style/order, validating any “interrupt local comm” message */ 31 | { BT_A, DP_NEUTRAL, SEQ_MASH, 16 }, 32 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 50 }, /* Wait for “Press L/R” menu */ 33 | ); 34 | 35 | if (mode == REAL_TO_VIRT) { 36 | SEND_BUTTON_SEQUENCE( 37 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Register as controller 1 */ 38 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 15 }, /* Wait for registration */ 39 | 40 | { BT_H, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Return to the main menu */ 41 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for the main menu */ 42 | ); 43 | 44 | go_to_game(); 45 | } 46 | } 47 | 48 | 49 | /* Go to the main menu, from the currently playing game or menu. */ 50 | void go_to_main_menu(void) 51 | { 52 | SEND_BUTTON_SEQUENCE( 53 | { BT_H, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Go to main menu */ 54 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for the main menu */ 55 | ); 56 | } 57 | 58 | 59 | /* Go back to the game, from the main menu. */ 60 | void go_to_game(void) 61 | { 62 | SEND_BUTTON_SEQUENCE( 63 | { BT_H, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Go back to the game */ 64 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 40 }, /* Wait for the game */ 65 | ); 66 | } 67 | 68 | 69 | /* Configure the Switch’s clock to manual mode */ 70 | void set_clock_to_manual_from_any(bool in_game) 71 | { 72 | if (in_game) { 73 | go_to_main_menu(); 74 | } 75 | 76 | SEND_BUTTON_SEQUENCE( 77 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 1 }, /* Switch Online button or News button (< v11) */ 78 | { BT_NONE, DP_RIGHT, SEQ_MASH, 6 }, /* Sleep button */ 79 | { BT_NONE, DP_LEFT, SEQ_MASH, 1 }, /* Settings button */ 80 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter settings */ 81 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for settings */ 82 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 14 }, /* Console settings */ 83 | { BT_NONE, DP_RIGHT, SEQ_MASH, 1 }, /* Update console button */ 84 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait for cursor */ 85 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 4 }, /* Date/time */ 86 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter date/time */ 87 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait date/time menu */ 88 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 2 }, /* TZ if auto/time set if man */ 89 | { BT_NONE, DP_TOP, SEQ_MASH, 1 }, /* auto/man if auto, TZ if man */ 90 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Set man if auto, else enter TZ */ 91 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait TZ menu, if any */ 92 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Wait for menu */ 93 | ); 94 | 95 | go_to_main_menu(); 96 | 97 | if (in_game) { 98 | go_to_game(); 99 | } 100 | } 101 | 102 | 103 | /* Configure the Switch’s clock to automatic mode */ 104 | void set_clock_to_auto_from_manual(bool in_game) 105 | { 106 | if (in_game) { 107 | go_to_main_menu(); 108 | } 109 | 110 | SEND_BUTTON_SEQUENCE( 111 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 1 }, /* Switch Online button or News button (< v11) */ 112 | { BT_NONE, DP_RIGHT, SEQ_MASH, 6 }, /* Sleep button */ 113 | { BT_NONE, DP_LEFT, SEQ_MASH, 1 }, /* Settings button */ 114 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter settings */ 115 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for settings */ 116 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 14 }, /* Console settings */ 117 | { BT_NONE, DP_RIGHT, SEQ_MASH, 1 }, /* Update console button */ 118 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait for cursor */ 119 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 4 }, /* Date/time */ 120 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter date/time */ 121 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait date/time menu */ 122 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Set to automatic */ 123 | ); 124 | 125 | go_to_main_menu(); 126 | 127 | if (in_game) { 128 | go_to_game(); 129 | } 130 | } 131 | 132 | 133 | /* Apply an offset to the Switch’s clock’s year. */ 134 | void change_clock_year(bool in_game, int8_t offset) 135 | { 136 | uint8_t button; 137 | uint8_t num; 138 | 139 | if (offset >= 0) { 140 | /* Increment year */ 141 | button = DP_TOP; 142 | num = (uint8_t)offset; 143 | } else { 144 | /* Decrement year */ 145 | button = DP_BOTTOM; 146 | num = (uint8_t)(-offset); 147 | } 148 | 149 | if (in_game) { 150 | go_to_main_menu(); 151 | } 152 | 153 | SEND_BUTTON_SEQUENCE( 154 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 1 }, /* Switch Online button or News button (< v11) */ 155 | { BT_NONE, DP_RIGHT, SEQ_MASH, 6 }, /* Sleep button */ 156 | { BT_NONE, DP_LEFT, SEQ_MASH, 1 }, /* Settings button */ 157 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter settings */ 158 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for settings */ 159 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 14 }, /* Console settings */ 160 | { BT_NONE, DP_RIGHT, SEQ_MASH, 1 }, /* Update console button */ 161 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait for cursor */ 162 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 4 }, /* Date/time */ 163 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter date/time */ 164 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Wait date/time menu */ 165 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 2 }, /* Time set */ 166 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Enter time set */ 167 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Wait for menu */ 168 | { BT_NONE, DP_RIGHT, SEQ_MASH, 2 }, /* Go to year */ 169 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Wait for cursor */ 170 | { BT_NONE, button, SEQ_MASH, num }, /* Change year */ 171 | { BT_A, DP_NEUTRAL, SEQ_MASH, 4 }, /* Go to OK and click it */ 172 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 2 }, /* Wait for menu */ 173 | ); 174 | 175 | go_to_main_menu(); 176 | 177 | if (in_game) { 178 | go_to_game(); 179 | } 180 | } 181 | 182 | 183 | /* Enter a number of the Switch keyboard. */ 184 | void type_number_on_keyboard(uint32_t number) 185 | { 186 | /* A 32-bit unsigned number can have up to 10 digits (+ terminator) */ 187 | int8_t digits[11]; 188 | int8_t* digits_ptr = digits + 10; 189 | 190 | *digits_ptr = -1; 191 | if (number == 0) { 192 | digits_ptr -= 1; 193 | *digits_ptr = 0; 194 | } else { 195 | while (number > 0) { 196 | digits_ptr -= 1; 197 | *digits_ptr = number % 10; 198 | number = number / 10; 199 | } 200 | } 201 | 202 | type_digits_on_keyboard(digits_ptr); 203 | } 204 | 205 | 206 | /* Enter a series of digits on the Switch keyboard. */ 207 | void type_digits_on_keyboard(const int8_t* digits) 208 | { 209 | /* 0 is represented by 10 since it’s after 9 on the keyboard */ 210 | int8_t selected_digit = 1; 211 | 212 | for (;;) { 213 | int8_t cur_digit = *digits; 214 | digits += 1; 215 | 216 | if (cur_digit < 0 || cur_digit > 9) { 217 | break; 218 | } 219 | 220 | if (cur_digit == 0) { 221 | cur_digit = 10; 222 | } 223 | 224 | const int8_t offset = selected_digit - cur_digit; 225 | if (offset < 0) { 226 | send_buttons(BT_NONE, DP_RIGHT, -offset); 227 | } else { 228 | send_buttons(BT_NONE, DP_LEFT, offset); 229 | } 230 | 231 | send_buttons(BT_A, DP_NEUTRAL, 1); 232 | selected_digit = cur_digit; 233 | } 234 | 235 | // Put the cursor back to the 1 digit 236 | send_buttons(BT_NONE, DP_LEFT, selected_digit - 1); 237 | } 238 | -------------------------------------------------------------------------------- /src/lib/automation-utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Automation functions that are not tied to a specific Switch game. 3 | */ 4 | 5 | #ifndef AUTOMATION_UTILS_H 6 | #define AUTOMATION_UTILS_H 7 | 8 | #include "automation.h" 9 | 10 | /* Controller switching mode */ 11 | enum switch_mode { 12 | VIRT_TO_REAL = 0, /* Virtual to real */ 13 | REAL_TO_VIRT = 1, /* Real to virtual */ 14 | }; 15 | 16 | /* 17 | * Switches controller 1 between the user’s “real” controller and the automated “virtual” 18 | * controller. 19 | * 20 | * This is used at startup to connect the virtual controller, and can be used during 21 | * automation so the user can temporarily perform manual actions. 22 | * 23 | * If mode is VIRT_TO_REAL, the automation will pause the game and activate the 24 | * controller setup menu. This will allow the user to press A on the real controller 25 | * to set it up again as controller 1, enabling them to play the game normally. 26 | * 27 | * If mode is REAL_TO_VIRT, the expected state is “main menu shown, cursor on the 28 | * currently playing game”. The virtual controller will (re-)connect (as controller 2), 29 | * activate the controller setup menu, validate it to become back controller 1, and 30 | * go back to the game. 31 | */ 32 | void switch_controller(enum switch_mode mode); 33 | 34 | /* 35 | * Go to the main menu, from the currently playing game or menu. 36 | */ 37 | void go_to_main_menu(void); 38 | 39 | /* 40 | * Go back to the game, from the main menu. 41 | */ 42 | void go_to_game(void); 43 | 44 | /* 45 | * Configure the Switch’s clock to manual mode, starting from the game or the 46 | * the main menu. 47 | * 48 | * The clock can be in automatic mode or be already in manual mode. 49 | * 50 | * The first parameter indicates that the operations starts in-game (and will end 51 | * in-game) 52 | */ 53 | void set_clock_to_manual_from_any(bool in_game); 54 | 55 | /* 56 | * Configure the Switch’s clock to automatic mode, starting from the game or the 57 | * the main menu. 58 | * 59 | * The clock needs to be in manual mode. 60 | * 61 | * The first parameter indicates that the operations starts in-game (and will end 62 | * in-game) 63 | */ 64 | void set_clock_to_auto_from_manual(bool in_game); 65 | 66 | /* 67 | * Apply an offset to the Switch’s clock’s year. 68 | * 69 | * The clock needs to be in manual mode. 70 | * 71 | * The first parameter indicates that the operations starts in-game (and will end 72 | * in-game) 73 | * The second parameter is the year offset to apply (-1: substract a year, 1: add a year) 74 | * The third parameter allows setting the RX/TX LEDs once the operation completes. 75 | */ 76 | void change_clock_year(bool in_game, int8_t offset); 77 | 78 | /* 79 | * Enter a number of the Switch keyboard. 80 | * 81 | * The keyboard must already be on the screen and its cursor on the number 1. 82 | * Leading zeroes are not included. 83 | */ 84 | void type_number_on_keyboard(uint32_t number); 85 | 86 | /* 87 | * Enter a series of digits on the Switch keyboard. 88 | * 89 | * Takes and array of integers between 0 and 9 and types them on the Switch 90 | * keyboard. The end of the array is signaled by an out-of-bounds integers (-1, 91 | * for instance). 92 | * The keyboard must already be on the screen and its cursor on the number 1. 93 | * Leading zeroes are not included. 94 | */ 95 | void type_digits_on_keyboard(const int8_t* digits); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/lib/automation.c: -------------------------------------------------------------------------------- 1 | #include "automation.h" 2 | 3 | #include "common.h" /* Must be included before setbaud.h (defines BAUD) */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | /* Data to send to the USB µC */ 11 | static struct { 12 | enum button_state buttons : 16; /* Button state */ 13 | enum d_pad_state d_pad : 8; /* D-pad state */ 14 | struct stick_coord l_stick; /* Left stick X/Y coordinate */ 15 | struct stick_coord r_stick; /* Right stick X/Y coordinate */ 16 | uint8_t magic_and_leds; /* Magic number and TX/RX LED state */ 17 | } sent_data; 18 | _Static_assert(sizeof(sent_data) == DATA_SIZE, "Incorrect sent data size"); 19 | 20 | /* 21 | * Init the automation: sets up the serial link to the USB µC, 22 | * and initializes the controller state with default data. 23 | * Returns true if the USB interface was just plugged in, false if the 24 | * main microcontroller was reset/reprogrammed afterwards. 25 | */ 26 | bool init_automation(void) 27 | { 28 | /* Set the serial link speed */ 29 | UBRR0H = UBRRH_VALUE; 30 | UBRR0L = UBRRL_VALUE; 31 | 32 | /* Check that ENABLE_DOUBLESPEED matches what was determined by setbaud */ 33 | #if USE_2X != ENABLE_DOUBLESPEED 34 | #error Change ENABLE_DOUBLESPEED and the UCSR0A enabling next line 35 | #endif 36 | 37 | UCSR0A &= ~_BV(U2X0); /* This bit must be set iff ENABLE_DOUBLESPEED = 1 */ 38 | 39 | /* Enable 8-bit mode, RX, and TX */ 40 | UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); 41 | UCSR0B = _BV(RXEN0) | _BV(TXEN0); 42 | 43 | /* Initialize the default data */ 44 | sent_data.buttons = BT_NONE; 45 | sent_data.d_pad = DP_NEUTRAL; 46 | sent_data.l_stick = S_NEUTRAL; 47 | sent_data.r_stick = S_NEUTRAL; 48 | sent_data.magic_and_leds = MAGIC_VALUE; 49 | 50 | /* Wait 12 ms for initial ready signal */ 51 | _delay_ms(12); 52 | if (bit_is_set(UCSR0A, RXC0)) { 53 | /* Retrieve ready signal byte */ 54 | uint8_t received = UDR0; 55 | if (received == INIT_SYNC_CHAR) { 56 | /* Initial sync done */ 57 | return true; 58 | } 59 | 60 | if (received != READY_FOR_DATA_CHAR) { 61 | /* Invalid character received */ 62 | panic(1); 63 | } 64 | 65 | /* READY_FOR_DATA_CHAR has been received; this may happen depending on 66 | the timing. Continue with the resync procedure. */ 67 | } 68 | 69 | for (uint8_t tries = 0 ; tries < DATA_SIZE ; tries += 1) { 70 | /* Send resync request */ 71 | loop_until_bit_is_set(UCSR0A, UDRE0); 72 | UDR0 = RE_SYNC_QUERY_BYTE; 73 | 74 | /* Wait for response */ 75 | _delay_ms(5); 76 | 77 | if (bit_is_set(UCSR0A, RXC0)) { 78 | /* Retrieve resync signal byte */ 79 | uint8_t received = UDR0; 80 | if (received == RE_SYNC_CHAR) { 81 | /* Re-sync done */ 82 | return false; 83 | } 84 | 85 | /* Invalid character received */ 86 | panic(2); 87 | } 88 | 89 | /* No response yet, the USB µC receive buffer may be not full yet; 90 | continue resync */ 91 | } 92 | 93 | /* Failed to resync, the USB µC is probably hung up */ 94 | panic(3); 95 | } 96 | 97 | 98 | /* Set the LED state to be sent in the next request */ 99 | void set_leds(enum led_state leds) 100 | { 101 | if (leds & TX_LED) { 102 | sent_data.magic_and_leds |= MAGIC_TX_STATE; 103 | } else { 104 | sent_data.magic_and_leds &= ~MAGIC_TX_STATE; 105 | } 106 | 107 | if (leds & RX_LED) { 108 | sent_data.magic_and_leds |= MAGIC_RX_STATE; 109 | } else { 110 | sent_data.magic_and_leds &= ~MAGIC_RX_STATE; 111 | } 112 | } 113 | 114 | 115 | /* Send an update with new button/controller state */ 116 | void send_update(enum button_state buttons, enum d_pad_state d_pad, 117 | struct stick_coord l_stick, struct stick_coord r_stick) 118 | { 119 | sent_data.buttons = buttons; 120 | sent_data.d_pad = d_pad; 121 | sent_data.l_stick = l_stick; 122 | sent_data.r_stick = r_stick; 123 | 124 | send_current(); 125 | } 126 | 127 | 128 | /* Send button press followed by a release. */ 129 | void send_buttons(enum button_state buttons, enum d_pad_state d_pad, 130 | uint8_t repeat_count) 131 | { 132 | while (repeat_count > 0) { 133 | send_update(buttons, d_pad, S_NEUTRAL, S_NEUTRAL); 134 | send_update(BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 135 | repeat_count -= 1; 136 | } 137 | } 138 | 139 | 140 | /* Send a button sequence */ 141 | void send_button_sequence(const struct button_d_pad_state sequence[], 142 | size_t sequence_length) 143 | { 144 | for (size_t pos = 0 ; pos < sequence_length ; pos += 1) { 145 | const struct button_d_pad_state* cur = &sequence[pos]; 146 | 147 | uint16_t repeat_count = cur->repeat_count; 148 | enum seq_mode mode = cur->mode; 149 | 150 | while (repeat_count > 0) { 151 | sent_data.buttons = cur->buttons; 152 | sent_data.d_pad = cur->d_pad; 153 | 154 | send_current(); 155 | 156 | if (mode == SEQ_MASH) { 157 | sent_data.buttons = BT_NONE; 158 | sent_data.d_pad = DP_NEUTRAL; 159 | send_current(); 160 | } 161 | 162 | repeat_count -= 1; 163 | } 164 | } 165 | } 166 | 167 | 168 | /* Send an update with the current state */ 169 | void send_current(void) 170 | { 171 | /* Wait for ready signal for USB µC */ 172 | loop_until_bit_is_set(UCSR0A, RXC0); 173 | 174 | /* Retrieve ready signal byte */ 175 | uint8_t received = UDR0; 176 | if (received != READY_FOR_DATA_CHAR) { 177 | panic(2); 178 | } 179 | 180 | const char* cur_sent_data = (const char*)&sent_data; 181 | 182 | for (uint8_t idx = 0 ; idx < sizeof(sent_data) ; idx += 1) { 183 | loop_until_bit_is_set(UCSR0A, UDRE0); 184 | UDR0 = cur_sent_data[idx]; 185 | } 186 | } 187 | 188 | 189 | /* Enter panic mode */ 190 | void panic(uint8_t mode) 191 | { 192 | const uint8_t portb_led = (1 << 5); 193 | 194 | /* Ensure the LED is powered on */ 195 | DDRB |= portb_led; 196 | 197 | if (mode == 0) { 198 | mode = 1; 199 | } 200 | 201 | for (;;) { 202 | for (uint8_t count = 0 ; count < mode ; count += 1) { 203 | PORTB |= portb_led; 204 | _delay_ms(500); 205 | PORTB &= ~portb_led; 206 | _delay_ms(500); 207 | } 208 | _delay_ms(1000); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/lib/automation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Switch controller automation features 3 | * 4 | * The USB interface emulating the Switch controller expects new data to be 5 | * sent to it on each “cycle” (5 USB reports, ~40 ms when plugged to a Switch). 6 | * The functions defined in this state allows sending new controller data at 7 | * the correct rate. The sent controller data also includes the state of the 8 | * TX and RX LEDs on the Arduino board, which are controlled by the USB 9 | * interface. 10 | */ 11 | 12 | #ifndef AUTOMATION_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | /* 19 | * Init the automation; must be called early at program start. 20 | * Returns true if the USB interface was just plugged in, false if the 21 | * main microcontroller was reset/reprogrammed afterwards. 22 | */ 23 | bool init_automation(void); 24 | 25 | /* LED state */ 26 | enum led_state { 27 | NO_LEDS = 0x00, /* Turn off RX and TX LEDs */ 28 | TX_LED = 0x01, /* Turn on TX LED */ 29 | RX_LED = 0x02, /* Turn on RX LED */ 30 | BOTH_LEDS = 0x03, /* Turn on RX and TX LEDs */ 31 | }; 32 | 33 | /* Stick coordinates */ 34 | struct stick_coord { 35 | uint8_t x; /* X position value */ 36 | uint8_t y; /* Y position value */ 37 | }; 38 | 39 | /* Return stick coordinates from X/Y positions. Usable in 40 | initializer lists or function calls. */ 41 | #define S_COORD(X, Y) ((struct stick_coord){(X), (Y)}) 42 | 43 | /* Predefined stick coordinates */ 44 | #define S_TOP S_COORD(128, 0) 45 | #define S_TOPRIGHT S_COORD(219, 37) 46 | #define S_RIGHT S_COORD(255, 128) 47 | #define S_BOTRIGHT S_COORD(219, 219) 48 | #define S_BOTTOM S_COORD(128, 255) 49 | #define S_BOTLEFT S_COORD(37, 219) 50 | #define S_LEFT S_COORD(0, 128) 51 | #define S_TOPLEFT S_COORD(37, 37) 52 | #define S_NEUTRAL S_COORD(128, 128) 53 | 54 | /* Internal macro for coordinate scaling */ 55 | #define S_SCALE_XY(COORD, M, VAL) \ 56 | (uint8_t)((((int16_t)COORD.M - 128) * VAL) / 255 + 128) 57 | 58 | /* Scale down the inclination of the stick, on a scale from 0 (not inclined, 59 | neutral position) to 255 (original value). For instance, S_TOP is the 60 | stick inclined 100% to the up direction, S_SCALED(S_TOP, 128) is the stick 61 | inclined 50% to the up direction, S_SCALED(S_TOP, 25) is the stick inclined 62 | 10% to the up direction, etc */ 63 | #define S_SCALED(COORD, VAL) \ 64 | S_COORD(S_SCALE_XY(COORD, x, VAL), S_SCALE_XY(COORD, y, VAL)) 65 | 66 | /* D-pad state */ 67 | enum d_pad_state { 68 | DP_TOP = 0, 69 | DP_TOPRIGHT = 1, 70 | DP_RIGHT = 2, 71 | DP_BOTRIGHT = 3, 72 | DP_BOTTOM = 4, 73 | DP_BOTLEFT = 5, 74 | DP_LEFT = 6, 75 | DP_TOPLEFT = 7, 76 | DP_NEUTRAL = 8, 77 | }; 78 | 79 | /* Buttons state */ 80 | enum button_state { 81 | BT_NONE = 0x0000, /* No buttons are pressed */ 82 | BT_Y = 0x0001, /* The Y button is pressed */ 83 | BT_B = 0x0002, /* The B button is pressed */ 84 | BT_A = 0x0004, /* The A button is pressed */ 85 | BT_X = 0x0008, /* The X button is pressed */ 86 | BT_L = 0x0010, /* The L button is pressed */ 87 | BT_R = 0x0020, /* The R button is pressed */ 88 | BT_ZL = 0x0040, /* The ZL button is pressed */ 89 | BT_ZR = 0x0080, /* The ZR button is pressed */ 90 | BT_M = 0x0100, /* The -/Select button is pressed */ 91 | BT_P = 0x0200, /* The +/Start button is pressed */ 92 | BT_H = 0x1000, /* The Home button is pressed */ 93 | BT_C = 0x2000, /* The Capture button is pressed */ 94 | 95 | }; 96 | 97 | /* Sequence run mode */ 98 | enum seq_mode { 99 | SEQ_HOLD = 0, /* Keep the buttons held during the specified number of cycles */ 100 | SEQ_MASH = 1, /* Insert a “release all buttons” cycle after each cycle */ 101 | }; 102 | 103 | /* Button and D-pad state, for sequence runs */ 104 | struct button_d_pad_state { 105 | enum button_state buttons : 16; /* Buttons */ 106 | enum d_pad_state d_pad : 4; /* D-pad */ 107 | enum seq_mode mode : 1; 108 | uint16_t repeat_count : 11; /* Number of cycles (max 2047) */ 109 | }; 110 | 111 | /* Set the LED state to be sent during the next update. */ 112 | void set_leds(enum led_state leds); 113 | 114 | /* Send an update with new button/controller state */ 115 | void send_update(enum button_state buttons, enum d_pad_state d_pad, 116 | struct stick_coord l_stick, struct stick_coord r_stick); 117 | 118 | /* 119 | * Send button press followed by a release. 120 | * The press/release sequence is repeated by the specified count. 121 | */ 122 | void send_buttons(enum button_state buttons, enum d_pad_state d_pad, 123 | uint8_t repeat_count); 124 | 125 | /* 126 | * Send a button sequence. 127 | * The first parameter is a pointer to an array of states to run in sequence. 128 | * The second parameter is the number of entries in the array. 129 | */ 130 | void send_button_sequence(const struct button_d_pad_state sequence[], 131 | size_t sequence_length); 132 | 133 | /* 134 | * Macro to simplify the use of send_button_sequence. 135 | * 136 | * Example usage: SEND_BUTTON_SEQUENCE({ BUTTON_A, DP_NEUTRAL, SEQ_HOLD, 5}, 137 | * { NO_BUTTONS, DP_TOP, SEQ_HOLD, 1 }); 138 | */ 139 | #define SEND_BUTTON_SEQUENCE(FIRST_STATE, ...) \ 140 | send_button_sequence((struct button_d_pad_state[]){ \ 141 | FIRST_STATE, __VA_ARGS__ }, sizeof((struct button_d_pad_state[]){ \ 142 | FIRST_STATE, __VA_ARGS__ }) / \ 143 | sizeof(struct button_d_pad_state)); 144 | 145 | /* 146 | * Send an update that reset the button/controller state to a neutral state 147 | * (no buttons pressed, sticks centered). This needs to be called if no updates 148 | * are going to be sent for a long period (more than a cycle length). 149 | */ 150 | __attribute__((always_inline)) inline void pause_automation(void) { 151 | send_update(BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 152 | } 153 | 154 | /* 155 | * Send an update with the current state. This be used after a call to set_leds 156 | * to send the new LED state immediately. 157 | */ 158 | void send_current(void); 159 | 160 | /* Enter panic mode; the L LED will repetitively blink the number of times 161 | specified in the parameters. Values 0 to 3 are used internally by the 162 | automation functions and should not be specified. Never returns. */ 163 | void panic(uint8_t mode) __attribute__((noreturn)); 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /src/lib/persist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Data persistence. Handles the storage of a uint32 value to EEPROM. 3 | */ 4 | 5 | #include "persist.h" 6 | 7 | #include 8 | 9 | /* 10 | * The code uses the 1024 first bytes of EEPROM to store the value, using a 11 | * “wear-levelling” mechanism. The EEPROM space is divided into 256 blocks and 12 | * the value is stored in one of the blocks. When a new value is set, the 13 | * current block is set to UINT32_MAX and the new value is written to the next 14 | * block. At startup, the current block to use is found by skipping all blocks 15 | * that contain UINT32_MAX. 16 | */ 17 | #define BLOCK_COUNT 256 18 | 19 | static uint8_t value_pos; 20 | 21 | /* Initializes data persistence. */ 22 | void init_persist(void) { 23 | for (uint16_t cur_pos = 0; cur_pos < BLOCK_COUNT ; cur_pos += 1) { 24 | uintptr_t offset = cur_pos * 4; 25 | uint32_t value = eeprom_read_dword((const uint32_t*)offset); 26 | if (value != UINT32_MAX) { 27 | value_pos = cur_pos; 28 | return; 29 | } 30 | } 31 | 32 | // All blocks are at UINT32_MAX; assume no data was ever written 33 | value_pos = 0; 34 | } 35 | 36 | /* Get the value from EEPROM. */ 37 | uint32_t persist_get_value(void) 38 | { 39 | uintptr_t offset = value_pos * 4; 40 | return eeprom_read_dword((const uint32_t*)offset); 41 | } 42 | 43 | 44 | /* Set the value to EEPROM. */ 45 | void persist_set_value(uint32_t value) 46 | { 47 | uintptr_t offset = value_pos * 4; 48 | eeprom_write_dword((uint32_t*)offset, UINT32_MAX); 49 | 50 | value_pos += 1; // Wraps to 0 when hitting block 256 51 | offset = value_pos * 4; 52 | eeprom_write_dword((uint32_t*)offset, value); 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/persist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Data persistence. Handles the storage of a uint32 value to EEPROM. 3 | */ 4 | 5 | #ifndef PERSIST_H 6 | #define PERSIST_H 7 | 8 | #include 9 | 10 | /* 11 | * Initializes data persistence. Must be called before calling other 12 | * functions. 13 | */ 14 | void init_persist(void); 15 | 16 | /* 17 | * Get the value from EEPROM. 18 | */ 19 | uint32_t persist_get_value(void); 20 | 21 | /* 22 | * Set the value to EEPROM. The UINT32_MAX value cannot be set. 23 | */ 24 | void persist_set_value(uint32_t value); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/lib/user-io.c: -------------------------------------------------------------------------------- 1 | #include "user-io.h" 2 | 3 | #include 4 | #include 5 | 6 | /* LED (digital pin 13) on port B */ 7 | #define PORTB_LED (1 << 5) 8 | 9 | /* Button on digital pin 12 port B */ 10 | #define PORTB_BUTTON (1 << 4) 11 | 12 | /* Buzzer (digital pin 2) on port D */ 13 | #define PORTD_BUZZER (1 << 2) 14 | 15 | /* Button minimum hold time (ms) -- avoid counting bounces as presses */ 16 | #define BUTTON_HOLD_TIME_MS 20 17 | 18 | 19 | /* Structure to track button presses */ 20 | struct button_info { 21 | uint8_t hold_time; /* Time the button was held down */ 22 | uint8_t count; /* Number of times the button was pressed */ 23 | }; 24 | 25 | 26 | /* Static functions */ 27 | static void track_button(struct button_info* info); 28 | static uint8_t get_tracked_presses(const struct button_info* info); 29 | 30 | 31 | /* Initializes the LED/button interface. */ 32 | void init_led_button(void) 33 | { 34 | /* Configure LED as output, buzzer as output, button as input */ 35 | DDRB = (DDRB | PORTB_LED) & (~PORTB_BUTTON); 36 | DDRD |= PORTD_BUZZER; 37 | 38 | /* Enable pullup on button */ 39 | PORTB |= PORTB_BUTTON; 40 | } 41 | 42 | 43 | /* Wait the specified amount of time for the button to be pressed. */ 44 | bool wait_for_button_timeout(uint16_t led_on_time_ms, uint16_t led_off_time_ms, 45 | uint16_t timeout_ms) 46 | { 47 | const uint16_t led_cycle_time_ms = led_on_time_ms + led_off_time_ms; 48 | uint16_t led_cycle_pos = 1; 49 | struct button_info info = {0, 0}; 50 | 51 | while (timeout_ms > 0) { 52 | if (led_cycle_pos == 1) { 53 | PORTB |= PORTB_LED; 54 | } else if (led_cycle_pos == led_on_time_ms) { 55 | PORTB &= ~PORTB_LED; 56 | } else if (led_cycle_pos == led_cycle_time_ms) { 57 | led_cycle_pos = 0; 58 | } 59 | 60 | track_button(&info); 61 | 62 | if (info.count) { 63 | break; 64 | } 65 | 66 | timeout_ms -= 1; 67 | led_cycle_pos += 1; 68 | } 69 | 70 | /* Will wait for the button to be released */ 71 | uint8_t presses = get_tracked_presses(&info); 72 | PORTB &= ~PORTB_LED; 73 | 74 | return presses > 0; 75 | } 76 | 77 | 78 | /* Blink the LED and wait for the user to press the button. */ 79 | uint8_t count_button_presses(uint16_t led_on_time_ms, 80 | uint16_t led_off_time_ms) 81 | { 82 | const uint16_t led_cycle_time_ms = led_on_time_ms + led_off_time_ms; 83 | uint16_t led_cycle_pos = 1; 84 | struct button_info info = {0, 0}; 85 | uint16_t timeout_ms = 0; 86 | 87 | while ((info.count == 0) || (timeout_ms > 0)) { 88 | if (led_cycle_pos == 1) { 89 | PORTB |= PORTB_LED; 90 | } else if (led_cycle_pos == led_on_time_ms) { 91 | PORTB &= ~PORTB_LED; 92 | } else if (led_cycle_pos == led_cycle_time_ms) { 93 | led_cycle_pos = 0; 94 | } 95 | 96 | track_button(&info); 97 | 98 | if (info.hold_time) { 99 | timeout_ms = 500; 100 | } 101 | 102 | timeout_ms -= 1; 103 | led_cycle_pos += 1; 104 | } 105 | 106 | PORTB &= ~PORTB_LED; 107 | 108 | /* Will wait for the button to be released */ 109 | return get_tracked_presses(&info); 110 | } 111 | 112 | /* Wait a fixed amount of time, blinking the LED */ 113 | uint8_t delay(uint16_t led_on_time_ms, uint16_t led_off_time_ms, 114 | uint16_t delay_ms) 115 | { 116 | uint16_t led_cycle_time_ms = led_on_time_ms + led_off_time_ms; 117 | uint16_t led_cycle_pos = 1; 118 | struct button_info info = {0, 0}; 119 | uint16_t remaining = delay_ms; 120 | 121 | while (remaining > 0) { 122 | if (led_on_time_ms != 0) { 123 | if (led_cycle_pos == 1) { 124 | PORTB |= PORTB_LED; 125 | } else if (led_cycle_pos == led_on_time_ms) { 126 | PORTB &= ~PORTB_LED; 127 | } else if (led_cycle_pos == led_cycle_time_ms) { 128 | led_cycle_pos = 0; 129 | } 130 | } 131 | 132 | track_button(&info); 133 | 134 | remaining -= 1; 135 | led_cycle_pos += 1; 136 | } 137 | 138 | PORTB &= ~PORTB_LED; 139 | 140 | if (delay_ms <= BUTTON_HOLD_TIME_MS) { 141 | /* The wait delay is lower than the minimum hold time, so 142 | get_tracked_presses will not return a correct number of press times. 143 | Instead, we just return 1 if the button was held at all. */ 144 | 145 | return (info.hold_time > 0) ? 1 : 0; 146 | } 147 | 148 | /* Will wait for the button to be released */ 149 | return get_tracked_presses(&info); 150 | } 151 | 152 | 153 | /* Emit a brief beep the buzzer. */ 154 | void beep(void) 155 | { 156 | PORTD |= PORTD_BUZZER; 157 | _delay_ms(1); 158 | PORTD &= ~PORTD_BUZZER; 159 | } 160 | 161 | 162 | /* 163 | * Track the button presses during roughly 1 ms. 164 | * The info struct must be initialized to all zeros before calling this 165 | * function. 166 | */ 167 | void track_button(struct button_info* info) 168 | { 169 | bool button_held = ((PINB & PORTB_BUTTON) == 0); 170 | 171 | if (button_held) { 172 | /* The button is held; increment the hold time */ 173 | info->hold_time += 1; 174 | 175 | } else { 176 | /* Check if the button was just released after being held for 177 | a sufficient time */ 178 | if (info->hold_time > BUTTON_HOLD_TIME_MS) { 179 | info->count += 1; 180 | } 181 | 182 | info->hold_time = 0; 183 | } 184 | 185 | _delay_ms(1); 186 | } 187 | 188 | 189 | /* 190 | * Count the button presses after a tracking operation. 191 | */ 192 | uint8_t get_tracked_presses(const struct button_info* info) 193 | { 194 | uint8_t count = info->count; 195 | 196 | /* Wait for the button to be released */ 197 | while ((PINB & PORTB_BUTTON) == 0) { 198 | /* Nothing */ 199 | } 200 | 201 | /* Count the last button press (if the button was still held the last time 202 | track_button was called */ 203 | if (info->hold_time > BUTTON_HOLD_TIME_MS) { 204 | count += 1; 205 | } 206 | 207 | return count; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /src/lib/user-io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * I/O user interface 3 | * 4 | * This file provides functions to provide a basic user interface using the 5 | * Arduino UNO LED (L) a push button connected beween pins 12 and GND, and 6 | * an optional buzzer connected between pins 2 and GND. 7 | * 8 | * The two other LEDs on the UNO board (RX/TX) are only accessible by the 9 | * USB interface and can be set using the automation API (see automation.h) 10 | */ 11 | 12 | #ifndef USER_IO_H 13 | #define USER_IO_H 14 | 15 | #include 16 | #include 17 | 18 | /* 19 | * Initializes the IO interface. Must be called before calling other functions 20 | * in this file. 21 | */ 22 | void init_led_button(void); 23 | 24 | /* 25 | * Wait the specified amount of time for the button to be pressed. If the 26 | * button is not pressed, this function returns false. If the button, is 27 | * pressed, this function waits for the button to be released, and returns 28 | * true. 29 | */ 30 | bool wait_for_button_timeout(uint16_t led_on_time_ms, uint16_t led_off_time_ms, 31 | uint16_t timeout_ms); 32 | 33 | /* 34 | * Blink the LED and wait for the user to press the button. Return the number 35 | * of presses. The user is allowed 500 ms between button presses before this 36 | * function returns. 37 | */ 38 | uint8_t count_button_presses(uint16_t led_on_time_ms, 39 | uint16_t led_off_time_ms); 40 | 41 | /* 42 | * Wait a fixed amount of time, blinking the LED (unless led_on_time_ms is 0). 43 | * 44 | * Returns the number of times the button was pressed. 45 | */ 46 | uint8_t delay(uint16_t led_on_time_ms, uint16_t led_off_time_ms, 47 | uint16_t delay_ms); 48 | 49 | /* 50 | * Emit a brief beep the buzzer. 51 | */ 52 | void beep(void); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/swsh/README.md: -------------------------------------------------------------------------------- 1 | Pokémon Sword/Shield automation 2 | =============================== 3 | 4 | This module automates some tasks in Pokémon Sword and Shield. 5 | 6 | Requirements 7 | ------------ 8 | 9 | You will need an Arduino UNO R3, an external Arduino programmer, and a 10 | pushbutton inserted in the Arduino board, between pins 13 and GND (on the top 11 | row). 12 | 13 | You can additionally install a buzzer between pins 2 and GND. 14 | 15 | See [the main README](../../README.md#required-hardware) for details. 16 | 17 | Installation 18 | ------------ 19 | 20 | Use `make` to build the `usb-iface.hex` and `swsh.hex` files. Flash 21 | `usb-iface.hex` to the USB interface microcontroller (ATmega16U2), and 22 | `swsh.hex` to the main microcontroller (ATmega328P). 23 | 24 | See the main README for the 25 | [required software](../../README.md#required-software), the 26 | [build procedure](../../README.md#building), and the 27 | [programming procedure](../../README.md#programming). 28 | 29 | Usage 30 | ----- 31 | 32 | Plug the Arduino to the Switch; the L LED on the Arduino board should start 33 | blinking rapidly, and the TX/RX LEDs should be off. 34 | 35 | To start the automation process, start Pokémon Sword/Shield (if it is not 36 | already), and put the game in the required state (which depends on the task to 37 | be automated; see below for details). 38 | 39 | Press Home to get to the Switch main menu (the selection should be on 40 | the game icon) and press the pushbutton on the Arduino board. The emulated 41 | controller will get auto-registered as controller 2, and then will access the 42 | controller settings to register as controller 1. It will then get back to the 43 | game, ready to control it. 44 | 45 | Once it’s done, the Arduino L LED will blink once per second, and both the 46 | RX and TX LEDs will be lit up. You are in the “main menu”, which allows you 47 | to select which automation feature to perform. Press the pushbutton on the 48 | board once to activate feature 1; twice to activate feature 2; etc. 49 | 50 | The different automation features are described below. 51 | 52 | ### Temporary control [Feature 1 — one button press] 53 | 54 | **Pre-requisites:** None 55 | 56 | Pokémon Sword/Shield only allows one controller to be enabled while the game 57 | is running; this features allows you to regain control of the game with your 58 | controller. 59 | 60 | When the feature is activated, the virtual controller will get back to the 61 | main menu and open the controller management menu, allowing you to press A on 62 | your controller to reconnect it. Once that’s done, you can close the menu 63 | and get back to the game. The Arduino L LED will blink rapidly. 64 | 65 | Once you’re ready to give back control to the virtual controller, press Home 66 | to get back to the main menu Switch main menu (the selection should be on 67 | the game icon) and press the pushbutton again. You will be back to the 68 | game, ready to activate another feature. 69 | 70 | ### Repeat press A [Feature 2 - two button presses] 71 | 72 | **Pre-requisites:** Be at a spot where repetitively pressing A makes sense, 73 | like in front of a member of the Digging Duo. 74 | 75 | When this feature is activated, the A button will be repetitively pressed and 76 | released. This allows getting items from the Digging Duo until you run out 77 | of Watts. 78 | 79 | Hold the pushbutton until the TX/RX LEDs stop blinking to exit this feature 80 | and return to the “main menu”. 81 | 82 | ### Max Raid Farming / Watts farming [Feature 3 - three button presses] 83 | 84 | This feature automates some aspects of Max Raid Battles, in order to get the 85 | Pokémon you want. 86 | 87 | When activated, you need to press the button again to select one of the three 88 | subfeatures, described below. 89 | 90 | #### Complete Max Raid Battle setup [Subfeature 1 - one button press] 91 | 92 | This subfeature is the main one; it gives you the wanted Max Raid Battle from 93 | an empty den, in a semi-automatic manner, using one Wishing Piece. 94 | 95 | **Pre-requisites:** 96 | 97 | - Be in front of a Pokémon Den. It should be completely empty (no light 98 | pillar, no glow — if it is glowing, press A to collect the Watts and close 99 | the dialog box). Your character should either face the center of the den or 100 | be on the bicycle (but check that you can actually interact with the den in 101 | that case). 102 | - Have at least one Wishing Piece. 103 | - In the X Menu, the Settings button should be in the bottom right corner (its 104 | default position). 105 | - The Switch’s clock should be set to roughly the current time, and that 106 | current time should not be just before midnight (letting this automation 107 | feature running when the day changes may cause undesired behavior). 108 | - Be in offline play before starting (online play can cause spurious 109 | “Disconnected from the server” messages that will mess up the automation). 110 | 111 | **Note:** This feature does not require a special text speed or 112 | automatic/manual clock configuration to be set beforehand. However, it will 113 | modify these parameters during operation, and when done, the text speed will be 114 | set to “Fast” and the clock setting will be set to “automatic”. 115 | 116 | Before starting, you need to select which den to use. The Pokémon that can be 117 | found in a Max Raid Battle depend on the den and the light pillar color (red: 118 | normal Raid Battle, bright purple: rare Raid Battle). Choose the appropriate 119 | den, make sure the appropriate pre-requisite are met, then start the automation 120 | feature. 121 | 122 | The process will start by setting the text speed to slow, and save the game. 123 | This is necessary the next step. It will then drop a Wishing Piece in the den, 124 | and pause the game after the light pillar start forming but before the game is 125 | saved. **Watch closely** as it happens: if the light pillar color (and thus the 126 | Raid Battle type) is what you want, **press the pushbutton** while the game is 127 | sitting on the home menu. If it isn’t, wait a couple of seconds; the process 128 | will quit/restart the game and re-drop the same Wishing Piece in a loop until 129 | you get the Max Raid Battle you want. Getting a rare Max Raid Battle (bright 130 | purple light pillar) may take a few minutes. 131 | 132 | Once you press the button, the process will move on to the next step; the text 133 | speed will be set back to fast, and the Switch’s clock sync will be set to 134 | manual. The Max Raid Battle menu will be then entered. 135 | 136 | If the menu does not shown the Pokémon that you want, wait a few seconds; the 137 | process will change the clock date twice to reset the Max Raid Battle. You will 138 | also gain Watts in the process, which may be a good way to farm them. 139 | 140 | When the Pokémon that you want is shown, press the pushbutton. The clock 141 | settings will be restored and you will get back control. At this point, you can 142 | safely close the Max Raid Battle menu without losing the Pokémon, since the 143 | automation process always enter this menu with the clock set to the current 144 | day. You can then start online play to battle the Pokémon, or save the game so 145 | you can re-battle the Pokémon in case it escapes (but note that Pokémon in rare 146 | Max Raid Battles — bright purple light pillar — have a 100% catch rate with any 147 | Pokéball so you will never lose them as long as you are not out of Pokéballs). 148 | 149 | Once you are done, you can go back to the Switch’s main menu and press the 150 | pushbutton to get back to the automation “main menu” where you can choose 151 | another automation task. If you restart the game without saving, the light 152 | pillar will still be up, and you can change the Max Raid Pokémon by activating 153 | subfeature 3 (see below for details). Note that you can only get one Pokémon 154 | per Wishing Piece used. 155 | 156 | #### Light pillar setup [Subfeature 2 - two button presses] 157 | 158 | This subfeature perform the setup of the light pillar (so you can choose which 159 | light pillar color/Raid type is created by the Wishing Piece), but does not 160 | start the Max Raid Battle itself. 161 | 162 | See the previous subfeature for requirements. When you get the appropriate Raid 163 | type and push the button, you will be able to regain control. Pressing the 164 | button again (from the Switch’s main menu) will get you back to the automation 165 | “main menu” where you can choose another automation task. 166 | 167 | This subfeature can be useful if you want to perform some actions after setting 168 | up the light pillar but before entering the Max Raid Battle (for instance, 169 | getting a Catch combo, in order to increase the likelihood of getting a shiny 170 | Pokémon in the Max Raid Battle; this cannot be done before the light pillar 171 | setup since it requires restarting the game). 172 | 173 | #### Light pillar setup [Subfeature 3 - three button presses] 174 | 175 | This subfeature changes the Pokémon available in the Max Raid battle. 176 | 177 | **Pre-requisites:** 178 | 179 | - Be in front of a Pokémon Den activated using a Wishing Piece (either 180 | manually or by using subfeatures 1 or 2). It should not be glowing. 181 | - In the X Menu, the Settings button should be in the bottom right corner (its 182 | default position). 183 | - The Switch’s clock should be set to roughly the current time, and that 184 | current time should not be just before midnight (letting this automation 185 | feature running when the day changes may cause undesired behavior). 186 | - Be in offline play before starting (online play can cause spurious 187 | “Disconnected from the server” messages that will mess up the automation). 188 | 189 | When activated, this feature will use clock changes to change the Pokémon 190 | available in the Max Raid Battle. Press the button once the Pokémon you want 191 | is shown; see subfeature 1 for details. 192 | 193 | Using this feature should not alter the Catch combo status (since it does not 194 | restart the game), so it may be possible to find shiny Pokémon in Max Raids 195 | using the following procedure (currently untested): 196 | 197 | 1. Activate subfeature 2 to get the appropriate light pillar for the Pokémon 198 | you want. 199 | 2. Get a Catch combo for that Pokémon. 200 | 3. Enter the Raid. If it is not the Pokémon you want, activate subfeature 3 201 | to change it. 202 | 4. When you get the Pokémon you want in the Max Raid Battle menu, enter the 203 | battle with a very weak Pokémon (in order to lose the battle) 204 | 5. If the Pokémon is indeed shiny, after you lose the battle, save the game. 205 | This will allow you to get it back even if you restart the game. You can 206 | then catch it (possibly in online mode). 207 | 6. If the Pokémon is not shiny, after you lose the battle, enter the Max Raid 208 | Battle again and activate subfeature 3 again. This will change the Pokémon 209 | so you can retry step 4, hopefully without resetting the Catch combo. 210 | 211 | ### Auto Breeding [Feature 4 - four button presses] 212 | 213 | This feature will automatically get Pokémon Eggs and hatch them. 214 | 215 | **Pre-requisites:** 216 | 217 | - Be next to the Bridge Field Pokémon Nursery. This is the nursery that will provide the 218 | Eggs to be hatched. 219 | - **Be on your bike.** 220 | - Your team needs to be full, with a Pokémon with the Flame Body ability in any slot 221 | **except** the second one. 222 | - Your bike needs to be fully upgraded. 223 | - In the X Menu, the Settings button should be in the bottom right corner and the Map 224 | button should be in the bottom left corner (their default position). 225 | 226 | You also need to known the Egg Cycle count of the Egg that are going to hatch. 227 | 228 | This feature depends on Eggs being available at the Nursery more often that they hatch, 229 | except for 5-cycle eggs. You may need to increase the changes of getting an Egg (by having 230 | the Oval Charm, and/or having two Pokémon having different IDs and/or species in the 231 | Nursery). 232 | 233 | **Note:** This feature does not require a special text speed to be set beforehand. 234 | However, it will modify these parameters during operation, and when done, the text speed 235 | will be set to “Fast”. 236 | 237 | After the feature is started, the LED will blink once per second. Press the button the 238 | following number of times, depending on the Egg Cycle number: 239 | 240 | | Egg Cycles | # presses | Approx. hatches / hour | 241 | | ---------- | --------- | ---------------------- | 242 | | 5 | 1 | 64 | 243 | | 10 | 2 | 60 | 244 | | 15 | 3 | 50 | 245 | | 20 | 4 | 40 | 246 | | 25 | 5 | 33 | 247 | | 30 | 6 | 30 | 248 | | 35 | 7 | 24 | 249 | | 40 | 8 | 22 | 250 | 251 | Once your selection is made, the buzzer will beep the number of times the button was 252 | pressed, to confirm your selection. 253 | 254 | At start, the following process is applied: 255 | 256 | 1. Set text speed to “fast” 257 | 2. Warp to the nearest location (which is the Bridge Field Pokémon Nursery) 258 | 3. Move around, to make sure an Egg is available. 259 | 260 | The following steps will then be applied in a loop: 261 | 262 | 1. Warp to the nearest location (which is the Bridge Field Pokémon Nursery) 263 | 2. Get an Egg from the Nursery Helper, put it in slot 2, sending slot 2 to Box 264 | 3. Move around in circles (not too near the grass to avoid collisions) 265 | 4. Wait during the hatching animation 266 | 5. (for egg cycles = 5) move around more to give more time for an Egg to be available 267 | 268 | The time for an Egg to hatch tends to vary a little bit, so the program will wait a bit 269 | more than necessary before stopping moving around and waiting for the Egg to hatch. The 270 | RX/TX LEDs light up during the wait; they should light up right after the “What?” dialog 271 | appears. 272 | 273 | During the Egg hatch animation (RX/TX lighted up, L blinking) you can interrupt the 274 | process by pressing the button. The process will give you back control. Press it again 275 | while on the Switch main menu (cursor on the game icon) to get back back into the 276 | automation “main menu” where you can choose another automation task. 277 | 278 | ### Release Boxes [Feature 5 - five button presses] 279 | 280 | This feature allows releasing one or multiple Boxes of Pokémon. 281 | 282 | **Pre-requisites:** 283 | 284 | - The Boxes menu is open on the first Box whose Pokémon are to be released. 285 | - The selection modes (X/Y buttons) are normal. 286 | - The Boxes whose Pokémon are to be released are completely full. 287 | 288 | When this feature is activated, it will start by moving the selection cursor 289 | to the top left Pokémon in the Box. It will then wait for input; press the 290 | button once to release all Pokémon in the current Box and move to the next Box 291 | (on the right); press the button twice when you are done. 292 | 293 | ### Scan Boxes [Feature 6 - six button presses] 294 | 295 | This feature will move the cursor along each Pokémon in Box, before moving to 296 | the next Box, and so on. 297 | 298 | **Pre-requisite:** The Boxes menu is open on the first Box which will be 299 | scanned. 300 | 301 | When this feature is activated, it will start by moving the selection cursor 302 | to the top left Pokémon in the Box, and then will start scanning all the Box 303 | then moving to the next Box. Hold the button down to interrupt the process and 304 | get back control. 305 | 306 | This feature allows checking the stats of many Pokémon at once, which is 307 | useful when you got many of them from Eggs. 308 | -------------------------------------------------------------------------------- /src/swsh/swsh.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Pokémon Sword/Shield automation 3 | */ 4 | 5 | #include 6 | 7 | #include "automation-utils.h" 8 | #include "user-io.h" 9 | 10 | /* Static functions */ 11 | static void temporary_control(void); 12 | static void repeat_press_a(void); 13 | static void max_raid_menu(void); 14 | static void max_raid_setup(void); 15 | static void light_pillar_setup_with_control(void); 16 | static void repeat_change_raid(void); 17 | static void repeat_change_raid_initial_confirm(void); 18 | static void light_pillar_setup(void); 19 | static void set_text_speed(bool fast_speed, bool save); 20 | static void use_wishing_piece_and_pause(void); 21 | static void restart_game(void); 22 | static void change_raid(void); 23 | static void auto_breeding(void); 24 | static void reposition_player(bool first_time); 25 | static void go_to_nursery_helper(void); 26 | static void get_egg(void); 27 | static void move_in_circles(uint16_t cycles, bool go_up_first); 28 | static bool hatch_egg(void); 29 | static void release_full_boxes(void); 30 | static void scan_boxes(void); 31 | static void position_box_cursor_topleft(void); 32 | static bool for_each_box_pos(bool top_left_start, bool (*callback)(void)); 33 | static bool release_from_box(void); 34 | static bool check_button_press(void); 35 | 36 | 37 | int main(void) 38 | { 39 | init_automation(); 40 | init_led_button(); 41 | 42 | /* Initial beep to confirm that the buzzer works */ 43 | beep(); 44 | 45 | /* Wait for the user to press the button (should be on the Switch main menu) */ 46 | count_button_presses(100, 100); 47 | 48 | /* Set the virtual controller as controller 1 */ 49 | switch_controller(REAL_TO_VIRT); 50 | 51 | for (;;) { 52 | /* Set the LEDs, and make sure automation is paused while in the 53 | menu */ 54 | set_leds(BOTH_LEDS); 55 | pause_automation(); 56 | 57 | /* Feature selection menu */ 58 | uint8_t count = count_button_presses(100, 900); 59 | 60 | for (uint8_t i = 0 ; i < count ; i += 1) { 61 | beep(); 62 | _delay_ms(100); 63 | } 64 | 65 | switch (count) { 66 | case 1: 67 | temporary_control(); 68 | break; 69 | 70 | case 2: 71 | repeat_press_a(); 72 | break; 73 | 74 | case 3: 75 | max_raid_menu(); 76 | break; 77 | 78 | case 4: 79 | auto_breeding(); 80 | break; 81 | 82 | case 5: 83 | release_full_boxes(); 84 | break; 85 | 86 | case 6: 87 | scan_boxes(); 88 | break; 89 | 90 | default: 91 | /* Wrong selection */ 92 | delay(100, 200, 1500); 93 | break; 94 | } 95 | } 96 | } 97 | 98 | 99 | /* 100 | * Temporary gives back control to the user by performing controller switch. 101 | */ 102 | void temporary_control(void) 103 | { 104 | set_leds(NO_LEDS); 105 | 106 | /* Allow the user to connect their controller back as controller 1 */ 107 | switch_controller(VIRT_TO_REAL); 108 | 109 | /* Wait for the user to press the button (should be on the Switch main menu) */ 110 | count_button_presses(100, 100); 111 | 112 | /* Set the virtual controller as controller 1 */ 113 | switch_controller(REAL_TO_VIRT); 114 | } 115 | 116 | 117 | /* 118 | * Press A repetitively until the button is pressed. 119 | */ 120 | static void repeat_press_a(void) 121 | { 122 | uint8_t count = 0; 123 | while (delay(0, 0, 50) == 0) { 124 | switch (count % 4) { 125 | case 0: 126 | set_leds(NO_LEDS); 127 | break; 128 | case 1: 129 | set_leds(TX_LED); 130 | break; 131 | case 2: 132 | set_leds(BOTH_LEDS); 133 | break; 134 | case 3: 135 | set_leds(RX_LED); 136 | break; 137 | } 138 | 139 | send_update(BT_A, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 140 | send_update(BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 141 | 142 | count += 1; 143 | } 144 | 145 | set_leds(NO_LEDS); 146 | send_update(BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL); 147 | 148 | _delay_ms(200); 149 | } 150 | 151 | 152 | /* 153 | * Max Raid Battle automation features 154 | */ 155 | void max_raid_menu(void) 156 | { 157 | /* Set the LEDs so the submenu is identifiable */ 158 | set_leds(TX_LED); 159 | pause_automation(); 160 | 161 | for (;;) { 162 | uint8_t subfeature = count_button_presses(500, 500); 163 | 164 | for (uint8_t i = 0 ; i < subfeature ; i += 1) { 165 | beep(); 166 | _delay_ms(200); 167 | } 168 | 169 | switch (subfeature) { 170 | case 1: /* Full Max Raid Battle automation */ 171 | max_raid_setup(); 172 | return; 173 | break; 174 | 175 | case 2: /* Light pillar setup */ 176 | light_pillar_setup_with_control(); 177 | return; 178 | break; 179 | 180 | case 3: /* Change existing Wishing Piece Raid */ 181 | repeat_change_raid(); 182 | return; 183 | break; 184 | 185 | default: 186 | /* Wrong selection */ 187 | delay(100, 200, 1500); 188 | break; 189 | } 190 | } 191 | } 192 | 193 | 194 | /* 195 | * Set up a light pillar for the appropriate Raid type (normal/rare) and 196 | * restart the Raid until the wanted Pokémon is available. 197 | */ 198 | void max_raid_setup(void) 199 | { 200 | /* First step: getting the appropriate light pillar */ 201 | light_pillar_setup(); 202 | 203 | /* While we are out of the game, set the Switch’s clock to manual */ 204 | set_clock_to_manual_from_any(/* in_game */ false); 205 | 206 | /* Get back to the game, wait a bit for it to finish saving and close the 207 | text box. */ 208 | set_leds(NO_LEDS); 209 | go_to_game(); 210 | 211 | SEND_BUTTON_SEQUENCE( 212 | { BT_B, DP_NEUTRAL, SEQ_MASH, 30 }, /* Close the dialogs */ 213 | ); 214 | 215 | /* Restore fast text speed */ 216 | set_text_speed(/* fast_speed */ true, /* save */ true); 217 | 218 | /* Open the Raid menu */ 219 | SEND_BUTTON_SEQUENCE( 220 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter Raid */ 221 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait */ 222 | ); 223 | 224 | repeat_change_raid_initial_confirm(); 225 | } 226 | 227 | 228 | /* 229 | * From an empty Den, get a light pillar of the appropriate type. 230 | * Restores the text speed and gives the user control before returning. 231 | */ 232 | void light_pillar_setup_with_control(void) 233 | { 234 | light_pillar_setup(); 235 | 236 | /* Get back to the game, wait a bit for it to finish saving and close 237 | the text box. */ 238 | set_leds(NO_LEDS); 239 | go_to_game(); 240 | 241 | SEND_BUTTON_SEQUENCE( 242 | { BT_B, DP_NEUTRAL, SEQ_MASH, 30 }, /* Close dialogs */ 243 | ); 244 | 245 | /* Restore fast text speed */ 246 | set_text_speed(/* fast_speed */ true, /* save */ false); 247 | 248 | /* Give control temporarily before returning */ 249 | temporary_control(); 250 | } 251 | 252 | 253 | /* 254 | * Repeatedly change the Raid from a Den activated with a Wishing Piece. The 255 | * first change is done immediately without confirmation, but the next ones ask 256 | * for user confirmation. Gives back control to the user once they are 257 | * satisfied with the Raid. 258 | * 259 | * Automatically sets the clock manual. 260 | */ 261 | void repeat_change_raid(void) 262 | { 263 | set_clock_to_manual_from_any(/* in_game */ true); 264 | 265 | /* Open the Raid menu */ 266 | SEND_BUTTON_SEQUENCE( 267 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter Raid */ 268 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait */ 269 | ); 270 | 271 | /* Perform first change immediately */ 272 | change_raid(); 273 | 274 | /* Wait for user confirmation on the next changes, and give them control */ 275 | repeat_change_raid_initial_confirm(); 276 | } 277 | 278 | 279 | /* 280 | * Repeatedly change the Raid from an open Raid menu created with a Wishing 281 | * Piece. Waits a bit for the user to confirm each change. Returns once the 282 | * user is satisfied with the Raid. 283 | * 284 | * The clock must be set to manual. 285 | */ 286 | void repeat_change_raid_initial_confirm(void) 287 | { 288 | for (;;) { 289 | /* Ask the user to look at the Pokémon in the Max Raid Battle */ 290 | beep(); 291 | 292 | /* Do the user wants to do this Raid? */ 293 | if (wait_for_button_timeout(250, 250, 5000)) { 294 | /* Restore the clock */ 295 | set_leds(NO_LEDS); 296 | set_clock_to_auto_from_manual(/* in_game */ true); 297 | 298 | /* Give control temporarily */ 299 | temporary_control(); 300 | 301 | /* Done */ 302 | break; 303 | } 304 | 305 | /* Change the Raid */ 306 | change_raid(); 307 | } 308 | } 309 | 310 | 311 | /* 312 | * From an empty Den, get a light pillar of the appropriate type. 313 | * When returning, text speed is slow and the game is paused. 314 | */ 315 | void light_pillar_setup(void) 316 | { 317 | /* Set text speed to slow so the light pillar appears before the game is 318 | saved; also save the game because it’s going to be restarted. */ 319 | set_text_speed(/* fast_speed */ false, /* save */ true); 320 | 321 | for (;;) { 322 | /* Ask the user to look at the light pillar color */ 323 | beep(); 324 | 325 | /* Drop the Wishing Piece in the den */ 326 | use_wishing_piece_and_pause(); 327 | 328 | /* Let the user choose what to do */ 329 | if (wait_for_button_timeout(250, 250, 5000)) { 330 | /* User confirmed, continue */ 331 | break; 332 | } 333 | 334 | /* Restart the game to re-try dropping the Wishing Piece */ 335 | restart_game(); 336 | } 337 | } 338 | 339 | 340 | /* 341 | * Open the parameters menu, set the text speed, and optionally save the game. 342 | * This requires the Parameters button on the X menu to be in the lower right corner. 343 | */ 344 | void set_text_speed(bool fast_speed, bool save) 345 | { 346 | uint8_t dir; /* Direction for speed selection */ 347 | uint8_t dely; /* Menu delay */ 348 | 349 | if (fast_speed) { 350 | dir = DP_RIGHT; 351 | dely = 10; 352 | } else { 353 | dir = DP_LEFT; 354 | dely = 25; 355 | } 356 | 357 | /* Uses held A button to makes the text go faster. */ 358 | SEND_BUTTON_SEQUENCE( 359 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open menu */ 360 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for menu */ 361 | { BT_NONE, DP_TOPLEFT, SEQ_HOLD, 25 }, /* Move to top/left position */ 362 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release the buttons */ 363 | 364 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 1 }, /* Move to Map position */ 365 | { BT_NONE, DP_LEFT, SEQ_MASH, 1 }, /* Move to Parameters position */ 366 | 367 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter Parameters */ 368 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 26 }, /* Wait for menu */ 369 | 370 | { BT_NONE, dir, SEQ_MASH, 2 }, /* Select speed */ 371 | 372 | { BT_A, DP_NEUTRAL, SEQ_HOLD, dely }, /* Validate parameters */ 373 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A to advance */ 374 | { BT_A, DP_NEUTRAL, SEQ_HOLD, dely }, /* Validate parameters */ 375 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A to advance */ 376 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Validate dialog */ 377 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for menu */ 378 | ); 379 | 380 | if (save) { 381 | SEND_BUTTON_SEQUENCE( 382 | { BT_R, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open Save menu */ 383 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 30 }, /* Wait for menu */ 384 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Save the game */ 385 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 80 }, /* Wait for save/menu closing */ 386 | ); 387 | } else { 388 | SEND_BUTTON_SEQUENCE( 389 | { BT_B, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Close the menu */ 390 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 30 }, /* Wait for menu closing */ 391 | ); 392 | } 393 | } 394 | 395 | 396 | /* 397 | * Drop a Wishing Piece in a den, and pause the game before it saves. This allows seeing 398 | * the light ray before allowing to game to save. 399 | * 400 | * When this function returns, the game can be either exited (which saves the Wishing 401 | * Piece) or resumed (which consumes it as the game is saved). 402 | */ 403 | void use_wishing_piece_and_pause(void) 404 | { 405 | /* A is held to speed up the text */ 406 | SEND_BUTTON_SEQUENCE( 407 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 35 }, /* First confirmation dialog */ 408 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 409 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 35 }, /* Validate dialog 1, open 2nd */ 410 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 411 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Validate second dialog */ 412 | 413 | /* It takes approximatively 2.6 seconds (80 video frames @ 30 FPS) between 414 | the A press and the “Save completed” dialog to show up (at slow text 415 | speed). The Home button needs to be pressed in the interval, but the timing 416 | of the events seems to vary depending on the runs. If Home is pressed too 417 | quickly, it has no effect, or the light pillar will not be visible; if it 418 | is pressed too late, the save has already been completed and the Raid will 419 | not be able to be restarted. 420 | 421 | An wait delay of 1.6 seconds (40 cycles) is used here, which hopefully 422 | works every time. Note that if the the player in not directly in front of 423 | the Raid den, it will turn to face it, which will add an additional delay. 424 | Pressing Home should still work in this case, but the light pillar may not 425 | be visible. 426 | 427 | Home is held for a little while; this prevents the game from occasionally 428 | skipping it. 429 | */ 430 | 431 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 40 }, /* Let the light pillar appear */ 432 | { BT_H, DP_NEUTRAL, SEQ_HOLD, 3 }, /* Interrupt save */ 433 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release buttons */ 434 | ); 435 | } 436 | 437 | 438 | /* 439 | * Restart the game (from the Switch main menu) 440 | */ 441 | void restart_game(void) 442 | { 443 | SEND_BUTTON_SEQUENCE( 444 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Ask to close game */ 445 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Wait for menu */ 446 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Confirm close */ 447 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 80 }, /* Wait for close */ 448 | { BT_A, DP_NEUTRAL, SEQ_MASH, 20 }, /* Relaunch game */ 449 | ); 450 | 451 | /* Wait for the game to start */ 452 | _delay_ms(17000); 453 | 454 | SEND_BUTTON_SEQUENCE( 455 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Validate start screen */ 456 | ); 457 | 458 | /* Wait a bit more than necessary for the game to load, so background loading will 459 | hopefully not interfere with the automation. */ 460 | _delay_ms(9000); 461 | } 462 | 463 | 464 | /* 465 | * From an open raid menu created with a Wishing Piece, start an stop the raid while 466 | * changing the system clock. This will cause the Pokémon in the raid to change. 467 | * 468 | * The clock must be set to manual. 469 | */ 470 | void change_raid(void) 471 | { 472 | /* Set the clock backwards */ 473 | set_leds(TX_LED); 474 | change_clock_year(/* in_game */ true, /* offset */ -1); 475 | 476 | /* Start the raid, but prepare to cancel it */ 477 | SEND_BUTTON_SEQUENCE( 478 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter “multiple combat” */ 479 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 55 }, /* Wait */ 480 | { BT_B, DP_NEUTRAL, SEQ_HOLD, 5 }, /* Open cancel menu (speed up text) */ 481 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Wait a bit */ 482 | ); 483 | 484 | /* Set the clock forward */ 485 | change_clock_year(/* in_game */ true, /* offset */ +1); 486 | 487 | /* Cancel the raid (exiting it), then re-enter it */ 488 | SEND_BUTTON_SEQUENCE( 489 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Cancel raid */ 490 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 120 }, /* Cancelling takes a loong time */ 491 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 15 }, /* Absorb the watts (speed up text) */ 492 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release the A button */ 493 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 15 }, /* Validate second message */ 494 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release the A button */ 495 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter raid */ 496 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait */ 497 | ); 498 | } 499 | 500 | 501 | /* 502 | * Automatically get Eggs from the Bridge Field Pokémon Nursery and hatch them. 503 | */ 504 | void auto_breeding(void) 505 | { 506 | /* Hatching time for each Egg cycles */ 507 | const struct { 508 | uint16_t hatch_time; /* Time passed spinning for the Egg to hatch */ 509 | uint16_t wait_time; /* Time passed spinning to get another Egg */ 510 | } egg_cycles[] = { 511 | { 350, 150 }, /* 5 Egg cycles, approx. 64 Eggs/hour */ 512 | { 560, 0 }, /* 10 Egg cycles, approx. 60 Eggs/hour */ 513 | { 1100, 0 }, /* 15 Egg cycles, approx. 50 Eggs/hour */ 514 | { 1400, 0 }, /* 20 Egg cycles, approx. 40 Eggs/hour */ 515 | { 1750, 0 }, /* 25 Egg cycles, approx. 33 Eggs/hour */ 516 | { 2050, 0 }, /* 30 Egg cycles, approx. 30 Eggs/hour */ 517 | { 2400, 0 }, /* 35 Egg cycles, approx. 24 Eggs/hour */ 518 | { 2700, 0 }, /* 40 Egg cycles, approx. 22 Eggs/hour */ 519 | }; 520 | 521 | /* Note that the automation is mashing B while on the bike, so its speed is irregular. 522 | This explains while the spin time is not linear compared to the Egg cycles. */ 523 | 524 | /* Select the egg cycle */ 525 | uint16_t hatch_time; 526 | uint16_t wait_time; 527 | 528 | /* Set the LEDs so the submenu is identifiable */ 529 | set_leds(RX_LED); 530 | pause_automation(); 531 | 532 | for (;;) { 533 | uint8_t cycle_idx = count_button_presses(500, 500) - 1; 534 | 535 | if (cycle_idx < (sizeof(egg_cycles) / sizeof(*egg_cycles))) { 536 | /* Selection OK, beep once per press */ 537 | for (uint8_t i = 0 ; i <= cycle_idx ; i += 1) { 538 | beep(); 539 | _delay_ms(200); 540 | } 541 | 542 | hatch_time = egg_cycles[cycle_idx].hatch_time; 543 | wait_time = egg_cycles[cycle_idx].wait_time; 544 | 545 | break; 546 | } 547 | 548 | /* Wrong selection */ 549 | delay(100, 200, 1500); 550 | } 551 | 552 | /* FIXME: Find a way to ensure the player character is on their bike instead of just 553 | toggling the state. For now, just require the player to start on the bike. */ 554 | #ifdef PUT_PLAYER_ON_BIKE 555 | SEND_BUTTON_SEQUENCE( 556 | { BT_P, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Get on bike */ 557 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for bike animation to finish */ 558 | ); 559 | #endif 560 | 561 | reposition_player(/* first_time */ true); 562 | go_to_nursery_helper(); 563 | 564 | /* We do not known if an egg is already available, so we just spin the first time */ 565 | move_in_circles(hatch_time + wait_time, /* go_up_first */ true); 566 | 567 | for (;;) { 568 | reposition_player(/* first_time */ false); 569 | go_to_nursery_helper(); 570 | get_egg(); 571 | move_in_circles(hatch_time, /* go_up_first */ true); 572 | 573 | if (hatch_egg()) { 574 | /* Operation stopped by the user */ 575 | break; 576 | } 577 | 578 | if (wait_time) { 579 | move_in_circles(wait_time, /* go_up_first */ false); 580 | } 581 | } 582 | 583 | /* Give temporary control before returning to the menu */ 584 | temporary_control(); 585 | } 586 | 587 | 588 | /* 589 | * Use the Flying Taxi to warp to the current position (the nursery) to reposition the 590 | * player. 591 | * 592 | * first_time must be true the first time this function is called; it will then put the 593 | * X menu’s cursor on the Map icon. It will also set the text speed to Fast. 594 | */ 595 | void reposition_player(bool first_time) 596 | { 597 | /* Uses held A button to makes the text go faster. */ 598 | 599 | set_leds(NO_LEDS); 600 | 601 | if (first_time) { 602 | SEND_BUTTON_SEQUENCE( 603 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open menu */ 604 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for menu */ 605 | { BT_NONE, DP_TOPLEFT, SEQ_HOLD, 25 }, /* Move to top/left position */ 606 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release the buttons */ 607 | 608 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 1 }, /* Move to Map position */ 609 | { BT_NONE, DP_LEFT, SEQ_MASH, 1 }, /* Move to Parameters position */ 610 | 611 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Enter Parameters */ 612 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 26 }, /* Wait for menu */ 613 | 614 | { BT_NONE, DP_RIGHT, SEQ_MASH, 2 }, /* Select speed */ 615 | 616 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Validate parameters */ 617 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A to advance */ 618 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Validate parameters */ 619 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A to advance */ 620 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Validate dialog */ 621 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for menu */ 622 | 623 | { BT_NONE, DP_RIGHT, SEQ_MASH, 1 }, /* Move to Map position */ 624 | ); 625 | } else { 626 | SEND_BUTTON_SEQUENCE( 627 | { BT_X, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open menu */ 628 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for menu */ 629 | ); 630 | } 631 | 632 | SEND_BUTTON_SEQUENCE( 633 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open map */ 634 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 55 }, /* Wait for map */ 635 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 15 }, /* Warp? */ 636 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 637 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Accept */ 638 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 60 }, /* Wait for warp to complete */ 639 | ); 640 | } 641 | 642 | 643 | /* 644 | * Go in front of the nursery helper. 645 | * reposition_player must be called first. 646 | */ 647 | void go_to_nursery_helper(void) 648 | { 649 | set_leds(TX_LED); 650 | 651 | send_update(BT_NONE, DP_NEUTRAL, S_SCALED(S_BOTLEFT, 25), S_NEUTRAL); 652 | 653 | for (uint8_t i = 0 ; i < 21 ; i += 1) { 654 | send_update(BT_NONE, DP_NEUTRAL, S_BOTTOM, S_NEUTRAL); 655 | } 656 | 657 | send_update(BT_NONE, DP_NEUTRAL, S_RIGHT, S_NEUTRAL); 658 | send_update(BT_NONE, DP_NEUTRAL, S_RIGHT, S_NEUTRAL); 659 | 660 | /* Reset the sticks and wait for the player to be standing still */ 661 | pause_automation(); 662 | _delay_ms(400); 663 | } 664 | 665 | 666 | /* 667 | * Get an Egg from the nursery helper. 668 | * go_to_nursery_helper must be called first. 669 | */ 670 | void get_egg(void) 671 | { 672 | SEND_BUTTON_SEQUENCE( 673 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Wait after movement */ 674 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 15 }, /* Open “accept egg” dialog */ 675 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 676 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Accept egg */ 677 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 75 }, /* Wait for dialog */ 678 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open “what do you want” dialog */ 679 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 50 }, /* Wait for dialog */ 680 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Choose “include in team” */ 681 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 682 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Open team dialog */ 683 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 45 }, /* Wait for dialog */ 684 | { BT_NONE, DP_BOTTOM, SEQ_MASH, 1 }, /* Go to second Pokémon */ 685 | { BT_A, DP_BOTTOM, SEQ_HOLD, 65 }, /* Select second Pokémon */ 686 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 687 | { BT_A, DP_BOTTOM, SEQ_HOLD, 35 }, /* Validate “… sent to box” dialog */ 688 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 689 | { BT_A, DP_BOTTOM, SEQ_MASH, 1 }, /* Validate “Take care” dialog */ 690 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for dialog to close */ 691 | ); 692 | } 693 | 694 | /* 695 | * Move in circle for the specified number of cycles. 696 | * go_to_nursery_helper must be called first for correct positioning. 697 | */ 698 | void move_in_circles(uint16_t cycles, bool go_up_first) 699 | { 700 | set_leds(RX_LED); 701 | 702 | if (go_up_first) { 703 | send_update(BT_NONE, DP_NEUTRAL, S_SCALED(S_TOP, 25), S_NEUTRAL); 704 | 705 | for (uint8_t i = 0 ; i < 10 ; i += 1) { 706 | send_update(BT_NONE, DP_NEUTRAL, S_TOP, S_NEUTRAL); 707 | send_update(BT_B, DP_NEUTRAL, S_TOP, S_NEUTRAL); 708 | } 709 | 710 | for (uint8_t i = 0 ; i < 50 ; i += 1) { 711 | send_update(BT_NONE, DP_NEUTRAL, S_TOPRIGHT, S_NEUTRAL); 712 | } 713 | } 714 | 715 | for (uint16_t i = 0 ; i < (cycles / 2) ; i += 1) { 716 | send_update(BT_NONE, DP_NEUTRAL, S_RIGHT, S_LEFT); 717 | send_update(BT_B, DP_NEUTRAL, S_RIGHT, S_LEFT); 718 | } 719 | 720 | /* Reset sticks position */ 721 | pause_automation(); 722 | } 723 | 724 | 725 | /* 726 | * Hatch an egg. The “What?” dialog must be shown on screen. 727 | * Returns true if the process was interrupted by the user. 728 | */ 729 | bool hatch_egg(void) 730 | { 731 | set_leds(BOTH_LEDS); 732 | 733 | SEND_BUTTON_SEQUENCE( 734 | { BT_A, DP_NEUTRAL, SEQ_MASH, 1 }, /* Validate “What?” dialog */ 735 | ) 736 | 737 | /* Egg hatching animation */ 738 | if (delay(250, 250, 12500)) { 739 | return true; 740 | } 741 | 742 | SEND_BUTTON_SEQUENCE( 743 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Speed up egg dialog text */ 744 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release A */ 745 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Validate egg dialog */ 746 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 80 }, /* Wait for fadeout */ 747 | ) 748 | 749 | return false; 750 | } 751 | 752 | 753 | /* 754 | * From the Box menu, releases all Pokémon in the current box, then move 755 | * to the next Box. User confirmation is asked for each Box. The Boxes must 756 | * be completely full. 757 | */ 758 | void release_full_boxes(void) 759 | { 760 | position_box_cursor_topleft(); 761 | 762 | bool cursor_topleft = true; 763 | 764 | for (;;) { 765 | /* Wait for user confirmation */ 766 | beep(); 767 | if (count_button_presses(500, 500) > 1) { 768 | /* User cancelled, we are done */ 769 | return; 770 | } 771 | 772 | /* Release the Box content */ 773 | for_each_box_pos(cursor_topleft, &release_from_box); 774 | 775 | /* The cursor position was toggled by the operation */ 776 | cursor_topleft ^= true; 777 | 778 | /* Move to the next Box */ 779 | SEND_BUTTON_SEQUENCE( 780 | { BT_R, DP_NEUTRAL, SEQ_MASH, 1 }, /* Next Box */ 781 | ); 782 | } 783 | } 784 | 785 | 786 | /* 787 | * Moves the cursor around each Pokémon in each Box, so their stats are 788 | * briefly shown. Stops the process when the button is held down. 789 | */ 790 | void scan_boxes(void) 791 | { 792 | position_box_cursor_topleft(); 793 | 794 | bool cursor_topleft = true; 795 | 796 | for (;;) { 797 | /* Move around in the Box */ 798 | if (for_each_box_pos(cursor_topleft, &check_button_press)) { 799 | /* The user stopped the operation */ 800 | break; 801 | } 802 | 803 | /* The cursor position was toggled by the operation */ 804 | cursor_topleft ^= true; 805 | 806 | /* Move to the next Box */ 807 | SEND_BUTTON_SEQUENCE( 808 | { BT_R, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Next Box */ 809 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Wait for Box */ 810 | ); 811 | } 812 | 813 | /* Give back control to the user */ 814 | temporary_control(); 815 | } 816 | 817 | 818 | /* 819 | * Position the cursor to the top left Pokémon in the Box menu. 820 | */ 821 | void position_box_cursor_topleft(void) 822 | { 823 | /* We hold the D-pad to put the cursor in a known position (mashing it does 824 | not work since it makes the cursor roll around the edges of the screen). 825 | We need to avoir getting the cursor on the Box title since the D-pad 826 | will start changing the selected Box. The screen layout also tend to 827 | make the cursor stuck in random positions if diagonal directions are 828 | used.*/ 829 | 830 | SEND_BUTTON_SEQUENCE( 831 | { BT_NONE, DP_BOTTOM, SEQ_HOLD, 25 }, /* Bottom row */ 832 | { BT_NONE, DP_LEFT, SEQ_HOLD, 25 }, /* Last Pokémon */ 833 | { BT_NONE, DP_TOP, SEQ_MASH, 5 }, /* First team Pokémon */ 834 | { BT_NONE, DP_RIGHT, SEQ_MASH, 1 }, /* Top/Left Box Pokémon */ 835 | ); 836 | } 837 | 838 | 839 | /* 840 | * Calls a callback after positioning the cursor on each Pokémon in the Box. 841 | * The starting position can either be the top left Pokémon or the bottom 842 | * right. The ending cursor position will be the reverse of the starting 843 | * cursor position. 844 | * Stops the process and return true if the callback returns true; else 845 | * returns false. 846 | */ 847 | bool for_each_box_pos(bool top_left_start, bool (*callback)(void)) 848 | { 849 | /* Do we go left on even rows (row 0, row 2, etc)? */ 850 | uint8_t left_on_even = (top_left_start ? 0 : 1); 851 | 852 | /* Which direction to use to move between rows? */ 853 | enum d_pad_state change_row_dir = (top_left_start ? DP_BOTTOM : DP_TOP); 854 | 855 | for (uint8_t row = 0 ; row < 5 ; row += 1) { 856 | for (uint8_t col = 0 ; col < 5 ; col += 1) { 857 | enum d_pad_state move_dir; 858 | 859 | if (callback()) { 860 | return true; 861 | } 862 | 863 | if ((row % 2) == left_on_even) { 864 | move_dir = DP_RIGHT; 865 | } else { 866 | move_dir = DP_LEFT; 867 | } 868 | 869 | SEND_BUTTON_SEQUENCE( 870 | { BT_NONE, move_dir, SEQ_MASH, 1 }, 871 | ); 872 | } 873 | 874 | if (callback()) { 875 | return true; 876 | } 877 | 878 | if (row < 4) { 879 | SEND_BUTTON_SEQUENCE( 880 | { BT_NONE, change_row_dir, SEQ_MASH, 1 }, 881 | ); 882 | } 883 | } 884 | 885 | return false; 886 | } 887 | 888 | 889 | /* 890 | * Release from the Box the Pokémon on which the cursor is on. 891 | * Returns false so for_each_box_pos continues execution. 892 | */ 893 | bool release_from_box(void) 894 | { 895 | SEND_BUTTON_SEQUENCE( 896 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 8 }, /* Open menu */ 897 | { BT_NONE, DP_TOP, SEQ_MASH, 2 }, /* Go to option */ 898 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 20 }, /* Select option */ 899 | { BT_NONE, DP_TOP, SEQ_MASH, 1 }, /* Go to Yes */ 900 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 25 }, /* Validate dialog 1 */ 901 | { BT_NONE, DP_NEUTRAL, SEQ_HOLD, 1 }, /* Release 1 */ 902 | { BT_A, DP_NEUTRAL, SEQ_HOLD, 10 }, /* Validate dialog 2 */ 903 | ); 904 | 905 | return false; 906 | } 907 | 908 | /* 909 | * Checks if the button is pressed for a short period of time. 910 | */ 911 | bool check_button_press(void) 912 | { 913 | return delay(0, 0, 20) != 0; 914 | } 915 | -------------------------------------------------------------------------------- /src/usb-iface/LUFAConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LUFA configuration file. 3 | */ 4 | 5 | #ifndef LUFA_CONFIG_H 6 | #define LUFA_CONFIG_H 7 | 8 | /* Set USB options statically */ 9 | #define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL) 10 | 11 | /* Device mode only */ 12 | #define USB_DEVICE_ONLY 13 | 14 | /* Store the USB descriptors in Flash memory */ 15 | #define USE_FLASH_DESCRIPTORS 16 | 17 | /* Static endpoint control size */ 18 | #define FIXED_CONTROL_ENDPOINT_SIZE 64 19 | 20 | /* Static configurations number */ 21 | #define FIXED_NUM_CONFIGURATIONS 1 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/usb-iface/Makefile: -------------------------------------------------------------------------------- 1 | # Specific Makefile to build the USB interface program using the LUFA library 2 | # and build system. 3 | 4 | MCU = atmega16u2 5 | ARCH = AVR8 6 | BOARD = UNO 7 | F_CPU = 16000000 8 | F_USB = $(F_CPU) 9 | OPTIMIZATION = s 10 | TARGET = usb-iface 11 | ifeq ($(strip $(STANDALONE_USB_IFACE)),) 12 | SRC = usb-iface.c usb-descriptors.c $(LUFA_SRC_USB) 13 | else 14 | $(warning Building the standalone USB interface) 15 | SRC = standalone-usb-iface.c usb-descriptors.c $(LUFA_SRC_USB) 16 | endif 17 | 18 | LUFA_PATH = ../../lufa/LUFA 19 | CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER 20 | 21 | all: 22 | 23 | include $(LUFA_PATH)/Build/lufa_core.mk 24 | include $(LUFA_PATH)/Build/lufa_sources.mk 25 | include $(LUFA_PATH)/Build/lufa_build.mk 26 | -------------------------------------------------------------------------------- /src/usb-iface/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Shared definition between the main µC and the USB µC code 3 | */ 4 | 5 | #ifndef COMMON_H 6 | #define COMMON_H 7 | 8 | /* Baud rate of the serial link */ 9 | #define BAUD 9600 10 | 11 | /* Double speed mode (must be 0 or 1) */ 12 | #define ENABLE_DOUBLESPEED 0 13 | 14 | /* Size of the messages transferred between the µC (which is also the size 15 | of a message sent to the USB host) */ 16 | #define DATA_SIZE 8 17 | 18 | /* Byte index in the message with the magic value */ 19 | #define MAGIC_INDEX (DATA_SIZE - 1) 20 | 21 | /* Mask of the bytes containing the magic value */ 22 | #define MAGIC_MASK 0xFC 23 | 24 | /* Magic value */ 25 | #define MAGIC_VALUE 0xAC 26 | 27 | /* TX LED state in the magic value byte */ 28 | #define MAGIC_TX_STATE 0x01 29 | 30 | /* RX LED state in the magic value byte */ 31 | #define MAGIC_RX_STATE 0x02 32 | 33 | /* Character sent by the USB µC for initial sync */ 34 | #define INIT_SYNC_CHAR 'I' 35 | 36 | /* Character sent by the USB µC for re-sync */ 37 | #define RE_SYNC_CHAR 'S' 38 | 39 | /* Character sent by the USB µC when data can be sent by the main µC */ 40 | #define READY_FOR_DATA_CHAR 'R' 41 | 42 | /* Byte repetitively sent by the main µC to request re-sync */ 43 | #define RE_SYNC_QUERY_BYTE 0x00 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/usb-iface/standalone-usb-iface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Standalone version of the code for the Arduino’s USB interface. Simulate a Nintendo 3 | * Switch controller, whose button presses/joystick movements are defined statically 4 | * in this file. Used for testing. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "usb-descriptors.h" 15 | 16 | 17 | /* Definitions */ 18 | /* Stick coordinates */ 19 | struct stick_coord { 20 | uint8_t x; /* X position value */ 21 | uint8_t y; /* Y position value */ 22 | }; 23 | 24 | /* stick coordinates */ 25 | #define S_NEUTRAL { 128, 128 } 26 | #define S_RIGHT { 255, 128 } 27 | 28 | /* D-pad state */ 29 | enum d_pad_state { 30 | DP_TOP = 0, 31 | DP_TOP_RIGHT = 1, 32 | DP_RIGHT = 2, 33 | DP_BOTTOM_RIGHT = 3, 34 | DP_BOTTOM = 4, 35 | DP_BOTTOM_LEFT = 5, 36 | DP_LEFT = 6, 37 | DP_TOP_LEFT = 7, 38 | DP_NEUTRAL = 8, 39 | }; 40 | 41 | /* Buttons state */ 42 | enum button_state { 43 | BT_NONE = 0x0000, /* No buttons are pressed */ 44 | BT_Y = 0x0001, /* The Y button is pressed */ 45 | BT_B = 0x0002, /* The B button is pressed */ 46 | BT_A = 0x0004, /* The A button is pressed */ 47 | BT_X = 0x0008, /* The X button is pressed */ 48 | BT_L = 0x0010, /* The L button is pressed */ 49 | BT_R = 0x0020, /* The R button is pressed */ 50 | BT_ZL = 0x0040, /* The ZL button is pressed */ 51 | BT_ZR = 0x0080, /* The ZR button is pressed */ 52 | BT_M = 0x0100, /* The -/Select button is pressed */ 53 | BT_P = 0x0200, /* The +/Start button is pressed */ 54 | BT_H = 0x1000, /* The Home button is pressed */ 55 | BT_C = 0x2000, /* The Capture button is pressed */ 56 | }; 57 | 58 | /* Data to send to the USB host, with the length */ 59 | struct usb_report_data { 60 | enum button_state buttons : 16; /* Button state */ 61 | enum d_pad_state d_pad : 8; /* D-pad state */ 62 | struct stick_coord l_stick; /* Left stick X/Y coordinate */ 63 | struct stick_coord r_stick; /* Right stick X/Y coordinate */ 64 | uint8_t repeat; /* Number of times to send this report */ 65 | }; 66 | _Static_assert(sizeof(struct usb_report_data) == 8, "Incorrect sent data size"); 67 | 68 | /* The report data sent to the USB host */ 69 | const struct usb_report_data report_data[] = { 70 | /* Go to the All Software page and waiting for it to show up */ 71 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 72 | { BT_L | BT_R, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 73 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 74 | { BT_A, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 75 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 76 | { BT_H, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 77 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 100, }, 78 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 200, }, 79 | { BT_A, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 10, }, 80 | 81 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 200, }, 82 | 83 | #if 0 84 | /* Using continuous press on the right d-pad takes ~68 reports to go to the sixth 85 | icon, because autorepeat takes time to start. Same thing with the L-stick. */ 86 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 68, }, 87 | #else 88 | /* Mashing the right d-pad every 5 reports takes 45 reports to go to the sixth 89 | icon. */ 90 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 5, }, 91 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 5, }, 92 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 5, }, 93 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 5, }, 94 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 5, }, 95 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 5, }, 96 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 5, }, 97 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 5, }, 98 | { BT_NONE, DP_RIGHT, S_NEUTRAL, S_NEUTRAL, 5, }, 99 | #endif 100 | 101 | { BT_NONE, DP_NEUTRAL, S_NEUTRAL, S_NEUTRAL, 1, }, 102 | }; 103 | 104 | /* Current report data to send */ 105 | static uint16_t report_idx = 0; 106 | 107 | /* Repeat count of the report data */ 108 | static uint8_t repeat_count = 0; 109 | 110 | /* Static functions */ 111 | static void process_hid_data(void); 112 | static void refresh_and_send_controller_data(void); 113 | static void panic(uint8_t mode); 114 | static void handle_panic_mode(void); 115 | 116 | /* Non-zero if in panic mode; indicate the number of LED blinks*/ 117 | static uint8_t panic_mode = 0; 118 | 119 | 120 | /* 121 | * Entry point 122 | */ 123 | int main(void) 124 | { 125 | /* Initial setup: Disable watchdog and clock divisor */ 126 | MCUSR &= ~(1 << WDRF); 127 | wdt_disable(); 128 | 129 | /* Initialize the LEDs and the serial link */ 130 | LEDs_Init(); 131 | 132 | LEDs_TurnOnLEDs(LEDMASK_TX); 133 | 134 | /* Initialize LUFI */ 135 | USB_Init(); 136 | 137 | /* Enable interrupts */ 138 | GlobalInterruptEnable(); 139 | 140 | for (;;) { 141 | /* Process HID requests/responses */ 142 | process_hid_data(); 143 | 144 | /* Run the general USB task */ 145 | USB_USBTask(); 146 | 147 | /* Handle LED updating in panic mode */ 148 | handle_panic_mode(); 149 | } 150 | } 151 | 152 | 153 | /* 154 | * Process HID data from and to the host. 155 | */ 156 | static void process_hid_data(void) 157 | { 158 | /* Wait for the device to be configured. */ 159 | if (USB_DeviceState != DEVICE_STATE_Configured) 160 | return; 161 | 162 | /* Process OUT data (from the host) */ 163 | Endpoint_SelectEndpoint(JOYSTICK_OUT_EPADDR); 164 | 165 | if (Endpoint_IsOUTReceived()) { 166 | LEDs_TurnOnLEDs(LEDMASK_RX); 167 | if (Endpoint_IsReadWriteAllowed()) { 168 | /* The host data is readable; read it */ 169 | uint8_t recv_data[8]; 170 | uint8_t status; 171 | 172 | do { 173 | status = Endpoint_Read_Stream_LE(&recv_data, sizeof(recv_data), 174 | NULL); 175 | } while (status != ENDPOINT_RWSTREAM_NoError); 176 | 177 | /* The data received is not used. */ 178 | } 179 | 180 | /* Acknowledge the OUT data */ 181 | Endpoint_ClearOUT(); 182 | } 183 | 184 | /* Provide IN data (to the host) */ 185 | Endpoint_SelectEndpoint(JOYSTICK_IN_EPADDR); 186 | 187 | if (Endpoint_IsINReady()) { 188 | refresh_and_send_controller_data(); 189 | } 190 | } 191 | 192 | 193 | /* 194 | * Refreshes the controller data and send it to the host. 195 | * The IN endpoint must be ready before calling this function. 196 | */ 197 | static void refresh_and_send_controller_data(void) 198 | { 199 | struct usb_report_data out_data = report_data[report_idx]; 200 | uint8_t status; 201 | out_data.repeat = 0; 202 | 203 | /* Send the data */ 204 | do { 205 | status = Endpoint_Write_Stream_LE(&out_data, sizeof(out_data), NULL); 206 | } while (status != ENDPOINT_RWSTREAM_NoError); 207 | 208 | /* Notify the IN data */ 209 | Endpoint_ClearIN(); 210 | 211 | repeat_count += 1; 212 | if (repeat_count >= report_data[report_idx].repeat) { 213 | repeat_count = 0; 214 | if (report_idx < ((sizeof(report_data) / sizeof(*report_data) - 1))) { 215 | report_idx += 1; 216 | } 217 | } 218 | } 219 | 220 | 221 | /* 222 | * Enter panic mode. The passed integer determine the number of times 223 | * the LEDs will blink. 224 | */ 225 | static void panic(uint8_t mode) 226 | { 227 | if (panic_mode) { 228 | return; 229 | } 230 | 231 | if (mode == 0) { 232 | mode = 1; 233 | } 234 | 235 | panic_mode = mode; 236 | } 237 | 238 | 239 | /* 240 | * Blink the LEDs in panic mode. 241 | */ 242 | static void handle_panic_mode(void) 243 | { 244 | static uint32_t count = 0; 245 | 246 | if (!panic_mode) { 247 | return; 248 | } 249 | 250 | count += 1; 251 | 252 | uint8_t frame = count >> 16; 253 | uint8_t blink_range = panic_mode * 2; 254 | uint8_t wait_range = blink_range + 4; 255 | 256 | if (frame < blink_range) { 257 | LEDs_SetAllLEDs(((frame % 2) == 0) ? LEDS_ALL_LEDS : LEDS_NO_LEDS); 258 | } else if (frame < wait_range) { 259 | LEDs_SetAllLEDs(LEDS_NO_LEDS); 260 | } else { 261 | count = 0; 262 | } 263 | } 264 | 265 | /* Called by LUFA to configure the device endpoints */ 266 | void EVENT_USB_Device_ConfigurationChanged(void) { 267 | Endpoint_ConfigureEndpoint(JOYSTICK_OUT_EPADDR, EP_TYPE_INTERRUPT, 268 | JOYSTICK_EPSIZE, 1); 269 | Endpoint_ConfigureEndpoint(JOYSTICK_IN_EPADDR, EP_TYPE_INTERRUPT, 270 | JOYSTICK_EPSIZE, 1); 271 | } 272 | -------------------------------------------------------------------------------- /src/usb-iface/usb-descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains the definition of the USB descriptors for the Nintendo 3 | * Switch controller. 4 | * Originally from: https://github.com/Bowarcky/pkmn-swsh-automation-tools/ 5 | */ 6 | 7 | #include "usb-descriptors.h" 8 | 9 | const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] = { 10 | HID_RI_USAGE_PAGE(8,1), /* Generic Desktop */ 11 | HID_RI_USAGE(8,5), /* Joystick */ 12 | HID_RI_COLLECTION(8,1), /* Application */ 13 | /* Buttons (2 bytes) */ 14 | HID_RI_LOGICAL_MINIMUM(8,0), 15 | HID_RI_LOGICAL_MAXIMUM(8,1), 16 | HID_RI_PHYSICAL_MINIMUM(8,0), 17 | HID_RI_PHYSICAL_MAXIMUM(8,1), 18 | /* The Switch will allow us to expand the original HORI descriptors to a full 16 buttons. */ 19 | /* The Switch will make use of 14 of those buttons. */ 20 | HID_RI_REPORT_SIZE(8,1), 21 | HID_RI_REPORT_COUNT(8,16), 22 | HID_RI_USAGE_PAGE(8,9), 23 | HID_RI_USAGE_MINIMUM(8,1), 24 | HID_RI_USAGE_MAXIMUM(8,16), 25 | HID_RI_INPUT(8,2), 26 | /* HAT Switch (1 nibble) */ 27 | HID_RI_USAGE_PAGE(8,1), 28 | HID_RI_LOGICAL_MAXIMUM(8,7), 29 | HID_RI_PHYSICAL_MAXIMUM(16,315), 30 | HID_RI_REPORT_SIZE(8,4), 31 | HID_RI_REPORT_COUNT(8,1), 32 | HID_RI_UNIT(8,20), 33 | HID_RI_USAGE(8,57), 34 | HID_RI_INPUT(8,66), 35 | /* There's an additional nibble here that's utilized as part of the Switch Pro Controller. */ 36 | /* I believe this -might- be separate U/D/L/R bits on the Switch Pro Controller, as they're utilized as four button descriptors on the Switch Pro Controller. */ 37 | HID_RI_UNIT(8,0), 38 | HID_RI_REPORT_COUNT(8,1), 39 | HID_RI_INPUT(8,1), 40 | /* Joystick (4 bytes) */ 41 | HID_RI_LOGICAL_MAXIMUM(16,255), 42 | HID_RI_PHYSICAL_MAXIMUM(16,255), 43 | HID_RI_USAGE(8,48), 44 | HID_RI_USAGE(8,49), 45 | HID_RI_USAGE(8,50), 46 | HID_RI_USAGE(8,53), 47 | HID_RI_REPORT_SIZE(8,8), 48 | HID_RI_REPORT_COUNT(8,4), 49 | HID_RI_INPUT(8,2), 50 | /* ??? Vendor Specific (1 byte) */ 51 | /* This byte requires additional investigation. */ 52 | HID_RI_USAGE_PAGE(16,65280), 53 | HID_RI_USAGE(8,32), 54 | HID_RI_REPORT_COUNT(8,1), 55 | HID_RI_INPUT(8,2), 56 | /* Output (8 bytes) */ 57 | /* Original observation of this suggests it to be a mirror of the inputs that we sent. */ 58 | /* The Switch requires us to have these descriptors available. */ 59 | HID_RI_USAGE(16,9761), 60 | HID_RI_REPORT_COUNT(8,8), 61 | HID_RI_OUTPUT(8,2), 62 | HID_RI_END_COLLECTION(0), 63 | }; 64 | 65 | /* Device Descriptor Structure */ 66 | const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = { 67 | .Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device}, 68 | 69 | .USBSpecification = VERSION_BCD(2,0,0), 70 | .Class = USB_CSCP_NoDeviceClass, 71 | .SubClass = USB_CSCP_NoDeviceSubclass, 72 | .Protocol = USB_CSCP_NoDeviceProtocol, 73 | 74 | .Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE, 75 | 76 | .VendorID = 0x0F0D, 77 | .ProductID = 0x0092, 78 | .ReleaseNumber = VERSION_BCD(1,0,0), 79 | 80 | .ManufacturerStrIndex = STRING_ID_Manufacturer, 81 | .ProductStrIndex = STRING_ID_Product, 82 | .SerialNumStrIndex = NO_DESCRIPTOR, 83 | 84 | .NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS 85 | }; 86 | 87 | /* Configuration Descriptor Structure */ 88 | const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { 89 | .Config = 90 | { 91 | .Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration}, 92 | 93 | .TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t), 94 | .TotalInterfaces = 1, 95 | 96 | .ConfigurationNumber = 1, 97 | .ConfigurationStrIndex = NO_DESCRIPTOR, 98 | 99 | .ConfigAttributes = 0x80, 100 | 101 | .MaxPowerConsumption = USB_CONFIG_POWER_MA(500) 102 | }, 103 | 104 | .HID_Interface = 105 | { 106 | .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, 107 | 108 | .InterfaceNumber = INTERFACE_ID_Joystick, 109 | .AlternateSetting = 0x00, 110 | 111 | .TotalEndpoints = 2, 112 | 113 | .Class = HID_CSCP_HIDClass, 114 | .SubClass = HID_CSCP_NonBootSubclass, 115 | .Protocol = HID_CSCP_NonBootProtocol, 116 | 117 | .InterfaceStrIndex = NO_DESCRIPTOR 118 | }, 119 | 120 | .HID_JoystickHID = 121 | { 122 | .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, 123 | 124 | .HIDSpec = VERSION_BCD(1,1,1), 125 | .CountryCode = 0x00, 126 | .TotalReportDescriptors = 1, 127 | .HIDReportType = HID_DTYPE_Report, 128 | .HIDReportLength = sizeof(JoystickReport) 129 | }, 130 | 131 | .HID_ReportINEndpoint = 132 | { 133 | .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, 134 | 135 | .EndpointAddress = JOYSTICK_IN_EPADDR, 136 | .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), 137 | .EndpointSize = JOYSTICK_EPSIZE, 138 | .PollingIntervalMS = 0x05 139 | }, 140 | 141 | .HID_ReportOUTEndpoint = 142 | { 143 | .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, 144 | 145 | .EndpointAddress = JOYSTICK_OUT_EPADDR, 146 | .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), 147 | .EndpointSize = JOYSTICK_EPSIZE, 148 | .PollingIntervalMS = 0x05 149 | }, 150 | }; 151 | 152 | /* Language Descriptor Structure */ 153 | const USB_Descriptor_String_t PROGMEM LanguageString = USB_STRING_DESCRIPTOR_ARRAY(LANGUAGE_ID_ENG); 154 | 155 | /* Manufacturer and Product Descriptor Strings */ 156 | const USB_Descriptor_String_t PROGMEM ManufacturerString = USB_STRING_DESCRIPTOR(L"HORI CO.,LTD."); 157 | const USB_Descriptor_String_t PROGMEM ProductString = USB_STRING_DESCRIPTOR(L"POKKEN CONTROLLER"); 158 | 159 | /* USB Device Callback - Get Descriptor */ 160 | uint16_t CALLBACK_USB_GetDescriptor( 161 | const uint16_t wValue, 162 | const uint16_t wIndex, 163 | const void** const DescriptorAddress 164 | ) { 165 | const uint8_t DescriptorType = (wValue >> 8); 166 | const uint8_t DescriptorNumber = (wValue & 0xFF); 167 | 168 | const void* Address = NULL; 169 | uint16_t Size = NO_DESCRIPTOR; 170 | 171 | switch (DescriptorType) 172 | { 173 | case DTYPE_Device: 174 | Address = &DeviceDescriptor; 175 | Size = sizeof(USB_Descriptor_Device_t); 176 | break; 177 | case DTYPE_Configuration: 178 | Address = &ConfigurationDescriptor; 179 | Size = sizeof(USB_Descriptor_Configuration_t); 180 | break; 181 | case DTYPE_String: 182 | switch (DescriptorNumber) 183 | { 184 | case STRING_ID_Language: 185 | Address = &LanguageString; 186 | Size = pgm_read_byte(&LanguageString.Header.Size); 187 | break; 188 | case STRING_ID_Manufacturer: 189 | Address = &ManufacturerString; 190 | Size = pgm_read_byte(&ManufacturerString.Header.Size); 191 | break; 192 | case STRING_ID_Product: 193 | Address = &ProductString; 194 | Size = pgm_read_byte(&ProductString.Header.Size); 195 | break; 196 | } 197 | 198 | break; 199 | case DTYPE_HID: 200 | Address = &ConfigurationDescriptor.HID_JoystickHID; 201 | Size = sizeof(USB_HID_Descriptor_HID_t); 202 | break; 203 | case DTYPE_Report: 204 | Address = &JoystickReport; 205 | Size = sizeof(JoystickReport); 206 | break; 207 | } 208 | 209 | *DescriptorAddress = Address; 210 | return Size; 211 | } 212 | -------------------------------------------------------------------------------- /src/usb-iface/usb-descriptors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains the definition of the USB descriptors for the Nintendo 3 | * Switch controller. 4 | * Originally from: https://github.com/Bowarcky/pkmn-swsh-automation-tools/ 5 | */ 6 | 7 | #ifndef USB_DESCRIPTORS_H 8 | #define USB_DESCRIPTORS_H 9 | 10 | #include 11 | 12 | #include 13 | 14 | /* Type Defines */ 15 | /* Device Configuration Descriptor Structure */ 16 | typedef struct 17 | { 18 | USB_Descriptor_Configuration_Header_t Config; 19 | 20 | /* Joystick HID Interface */ 21 | USB_Descriptor_Interface_t HID_Interface; 22 | USB_HID_Descriptor_HID_t HID_JoystickHID; 23 | USB_Descriptor_Endpoint_t HID_ReportOUTEndpoint; 24 | USB_Descriptor_Endpoint_t HID_ReportINEndpoint; 25 | } USB_Descriptor_Configuration_t; 26 | 27 | /* Device Interface Descriptor IDs */ 28 | enum InterfaceDescriptors_t 29 | { 30 | INTERFACE_ID_Joystick = 0, /**< Joystick interface descriptor ID */ 31 | }; 32 | 33 | /* Device String Descriptor IDs */ 34 | enum StringDescriptors_t 35 | { 36 | STRING_ID_Language = 0, /* Supported Languages string descriptor ID (must be zero) */ 37 | STRING_ID_Manufacturer = 1, /* Manufacturer string ID */ 38 | STRING_ID_Product = 2, /* Product string ID */ 39 | }; 40 | 41 | /* Macros */ 42 | /* Endpoint Addresses */ 43 | #define JOYSTICK_IN_EPADDR (ENDPOINT_DIR_IN | 1) 44 | #define JOYSTICK_OUT_EPADDR (ENDPOINT_DIR_OUT | 2) 45 | /* HID Endpoint Size */ 46 | /* The Switch -needs- this to be 64. */ 47 | /* The Wii U is flexible, allowing us to use the default of 8 (which did not match the original Hori descriptors). */ 48 | #define JOYSTICK_EPSIZE 64 49 | /* Descriptor Header Type - HID Class HID Descriptor */ 50 | #define DTYPE_HID 0x21 51 | /* Descriptor Header Type - HID Class HID Report Descriptor */ 52 | #define DTYPE_Report 0x22 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/usb-iface/usb-iface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Code for the Arduino’s USB interface. Simulate a Nintendo Switch controller, 3 | * whose button presses/joystick movements are received on the serial 4 | * interface. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "usb-descriptors.h" 15 | #include "common.h" 16 | 17 | 18 | /* Static functions */ 19 | static void process_hid_data(void); 20 | static void refresh_and_send_controller_data(void); 21 | static bool refresh_controller_data(void); 22 | static void handle_serial_comm(void); 23 | static void handle_recv_byte(uint8_t recv_byte); 24 | static void panic(uint8_t mode); 25 | static void handle_panic_mode(void); 26 | 27 | 28 | /* 29 | * Data sent to the USB host when there is no data available. 30 | * The data represents no buttons pressed, and control sticks centered. 31 | */ 32 | const uint8_t neutral_controller_data[DATA_SIZE] = { 33 | 0, 0, 0x08, 128, 128, 128, 128, MAGIC_VALUE, 34 | }; 35 | 36 | /* Output data that will be sent to the host */ 37 | static uint8_t out_data[DATA_SIZE]; 38 | 39 | /* Receive buffer from the main µC */ 40 | static uint8_t recv_buffer[DATA_SIZE]; 41 | 42 | /* Number of bytes in the receive buffer */ 43 | static uint8_t recv_buffer_count; 44 | 45 | /* Non-zero if in panic mode; indicate the number of LED blinks*/ 46 | static uint8_t panic_mode = 0; 47 | 48 | 49 | /* 50 | * Entry point 51 | */ 52 | int main(void) 53 | { 54 | /* Initial setup: Disable watchdog and clock divisor */ 55 | MCUSR &= ~(1 << WDRF); 56 | wdt_disable(); 57 | 58 | /* Initialize the LEDs and the serial link */ 59 | LEDs_Init(); 60 | Serial_Init(BAUD, ENABLE_DOUBLESPEED); 61 | 62 | /* Send the initial sync byte to the main µC */ 63 | _delay_ms(11); 64 | Serial_SendByte(INIT_SYNC_CHAR); 65 | 66 | /* Initialize LUFI */ 67 | USB_Init(); 68 | 69 | /* Enable interrupts */ 70 | GlobalInterruptEnable(); 71 | 72 | /* Start with a receive buffer full of neutral controller data, so it is 73 | taken into account for the first output message to the host, and 74 | the ready signal is sent to the main µC */ 75 | memcpy(recv_buffer, neutral_controller_data, sizeof(recv_buffer)); 76 | recv_buffer_count = DATA_SIZE; 77 | 78 | for (;;) { 79 | /* Handle serial reception */ 80 | handle_serial_comm(); 81 | 82 | /* Process HID requests/responses */ 83 | process_hid_data(); 84 | 85 | /* Run the general USB task */ 86 | USB_USBTask(); 87 | 88 | /* Handle LED updating in panic mode */ 89 | handle_panic_mode(); 90 | } 91 | } 92 | 93 | 94 | /* 95 | * Process HID data from and to the host. 96 | */ 97 | void process_hid_data(void) 98 | { 99 | /* Wait for the device to be configured. */ 100 | if (USB_DeviceState != DEVICE_STATE_Configured) 101 | return; 102 | 103 | /* Process OUT data (from the host) */ 104 | Endpoint_SelectEndpoint(JOYSTICK_OUT_EPADDR); 105 | 106 | if (Endpoint_IsOUTReceived()) { 107 | if (Endpoint_IsReadWriteAllowed()) { 108 | /* The host data is readable; read it */ 109 | uint8_t recv_data[DATA_SIZE]; 110 | uint8_t status; 111 | 112 | do { 113 | status = Endpoint_Read_Stream_LE(&recv_data, sizeof(recv_data), 114 | NULL); 115 | } while (status != ENDPOINT_RWSTREAM_NoError); 116 | 117 | /* The data received is not used. */ 118 | } 119 | 120 | /* Acknowledge the OUT data */ 121 | Endpoint_ClearOUT(); 122 | } 123 | 124 | /* Provide IN data (to the host) */ 125 | Endpoint_SelectEndpoint(JOYSTICK_IN_EPADDR); 126 | 127 | if (Endpoint_IsINReady()) { 128 | refresh_and_send_controller_data(); 129 | } 130 | } 131 | 132 | 133 | /* 134 | * Refreshes the controller data (if needed) and send it to the host. 135 | * The IN endpoint must be ready before calling this function. 136 | */ 137 | void refresh_and_send_controller_data(void) 138 | { 139 | uint8_t status; 140 | static uint8_t send_count = 0; 141 | bool notify_main_uc = false; 142 | 143 | if (send_count == 0) { 144 | /* Need to refresh the controller data on this cycle */ 145 | 146 | notify_main_uc = refresh_controller_data(); 147 | } 148 | 149 | /* Send the data */ 150 | do { 151 | status = Endpoint_Write_Stream_LE(out_data, sizeof(out_data), NULL); 152 | } while (status != ENDPOINT_RWSTREAM_NoError); 153 | 154 | /* Notify the IN data */ 155 | Endpoint_ClearIN(); 156 | 157 | if (notify_main_uc) { 158 | Serial_SendByte(READY_FOR_DATA_CHAR); 159 | } 160 | 161 | send_count += 1; 162 | if (send_count == 5) { 163 | send_count = 0; 164 | } 165 | } 166 | 167 | 168 | /* 169 | * Refresh the controller data to be sent to the host. 170 | * Returns true if the main µC should be notified that it can send more data. 171 | */ 172 | bool refresh_controller_data(void) 173 | { 174 | bool notify_main_uc = false; 175 | static uint8_t prev_recv_count = 0; 176 | 177 | if (panic_mode) { 178 | return false; 179 | } 180 | 181 | /* Refresh the controller data */ 182 | if (recv_buffer_count == DATA_SIZE) { 183 | uint8_t magic_data = recv_buffer[MAGIC_INDEX]; 184 | 185 | if ((magic_data & MAGIC_MASK) == MAGIC_VALUE) { 186 | /* Magic value OK, update LED state and controller data */ 187 | uint8_t new_led_state = 0; 188 | 189 | if (magic_data & MAGIC_TX_STATE) { 190 | new_led_state |= LEDMASK_TX; 191 | } 192 | 193 | if (magic_data & MAGIC_RX_STATE) { 194 | new_led_state |= LEDMASK_RX; 195 | } 196 | 197 | LEDs_SetAllLEDs(new_led_state); 198 | 199 | /* Don’t copy the magic byte to the controller data, leave it 0 */ 200 | memcpy(out_data, recv_buffer, DATA_SIZE - 1); 201 | 202 | /* Empty the receive buffer and notify the main µC */ 203 | memset(recv_buffer, 0, sizeof(recv_buffer)); 204 | recv_buffer_count = 0; 205 | notify_main_uc = true; 206 | } else { 207 | /* Invalid data received */ 208 | panic(2); 209 | } 210 | 211 | } else if (memcmp(out_data, neutral_controller_data, DATA_SIZE - 1) != 0) { 212 | /* The receive buffer was not full, and the output data is not neutral. */ 213 | if (recv_buffer_count == 0) { 214 | /* The main µC did not send any message on this cycle */ 215 | panic(2); 216 | } else { 217 | /* The main µC failed to send a message sufficiently quickly. Note that this 218 | is not an error if the current output data is neutral; after sending 219 | neutral data, the main µC is allowed to sleep for an arbitrary amount of 220 | time. When it starts sending data again, it’s not synchronized with 221 | the USB µC, so this function may be called while it’s sending data. 222 | 223 | When the main µC starts sending non-neutral controller data messages, it’s 224 | not supposed to sleep for long periods of time; this means it will stay 225 | roughly synchronized with the USB µC’s cycles, and received messages 226 | should always be complete when this function is called. */ 227 | panic(3); 228 | } 229 | } else if ((recv_buffer_count != 0) && (prev_recv_count == recv_buffer_count)) { 230 | /* The output data is neutral, and the main µC sent some data, but no data 231 | was received during one full cycle. */ 232 | panic(4); 233 | } 234 | 235 | prev_recv_count = recv_buffer_count; 236 | 237 | return notify_main_uc; 238 | } 239 | 240 | 241 | /* 242 | * Receive and process data from the main µC on the serial link. 243 | */ 244 | void handle_serial_comm(void) 245 | { 246 | int16_t recv_val; 247 | 248 | for (;;) { 249 | recv_val = Serial_ReceiveByte(); 250 | if (recv_val < 0) { 251 | return; 252 | } 253 | 254 | handle_recv_byte((uint8_t)recv_val); 255 | } 256 | } 257 | 258 | 259 | /* 260 | * Handle a received byte from the main µC. 261 | */ 262 | void handle_recv_byte(uint8_t recv_byte) 263 | { 264 | if ((recv_buffer_count >= (DATA_SIZE) - 1) && (recv_byte == RE_SYNC_QUERY_BYTE)) { 265 | /* Re-sync query received from the main µC; acknowledge it and 266 | reset controller data. The main µC will receive a new data 267 | query on the next cycle. */ 268 | Serial_SendByte(RE_SYNC_CHAR); 269 | 270 | panic_mode = 0; 271 | 272 | memcpy(recv_buffer, neutral_controller_data, sizeof(recv_buffer)); 273 | recv_buffer_count = DATA_SIZE; 274 | 275 | } else if (recv_buffer_count < DATA_SIZE) { 276 | /* Normal data received */ 277 | recv_buffer[recv_buffer_count] = recv_byte; 278 | recv_buffer_count += 1; 279 | 280 | } else { 281 | /* Data received while the buffer was full */ 282 | panic(4); 283 | } 284 | } 285 | 286 | 287 | /* 288 | * Enter panic mode. The passed integer determine the number of times 289 | * the LEDs will blink. 290 | */ 291 | void panic(uint8_t mode) 292 | { 293 | if (panic_mode) { 294 | return; 295 | } 296 | 297 | if (mode == 0) { 298 | mode = 1; 299 | } 300 | 301 | panic_mode = mode; 302 | 303 | memcpy(out_data, neutral_controller_data, sizeof(out_data)); 304 | } 305 | 306 | 307 | /* 308 | * Blink the LEDs in panic mode. 309 | */ 310 | void handle_panic_mode(void) 311 | { 312 | static uint32_t count = 0; 313 | 314 | if (!panic_mode) { 315 | return; 316 | } 317 | 318 | count += 1; 319 | 320 | uint8_t frame = count >> 16; 321 | uint8_t blink_range = panic_mode * 2; 322 | uint8_t wait_range = blink_range + 4; 323 | 324 | if (frame < blink_range) { 325 | LEDs_SetAllLEDs(((frame % 2) == 0) ? LEDS_ALL_LEDS : LEDS_NO_LEDS); 326 | } else if (frame < wait_range) { 327 | LEDs_SetAllLEDs(LEDS_NO_LEDS); 328 | } else { 329 | count = 0; 330 | } 331 | } 332 | 333 | /* Called by LUFA to configure the device endpoints */ 334 | void EVENT_USB_Device_ConfigurationChanged(void) { 335 | Endpoint_ConfigureEndpoint(JOYSTICK_OUT_EPADDR, EP_TYPE_INTERRUPT, 336 | JOYSTICK_EPSIZE, 1); 337 | Endpoint_ConfigureEndpoint(JOYSTICK_IN_EPADDR, EP_TYPE_INTERRUPT, 338 | JOYSTICK_EPSIZE, 1); 339 | } 340 | -------------------------------------------------------------------------------- /tools/read_reset_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Reads the reset count from the Arduino EEPROM. 5 | """ 6 | 7 | import argparse 8 | import subprocess 9 | import sys 10 | 11 | 12 | def run(): 13 | """ 14 | Program entry point 15 | """ 16 | 17 | parser = argparse.ArgumentParser(description=__doc__) 18 | parser.add_argument('--programmer', default='avrispmkii', 19 | help="Type of programmer connected to the Arduino") 20 | args = parser.parse_args() 21 | 22 | cmd = ['avrdude', '-qq', '-p', 'atmega328p', '-c', args.programmer, '-P', 23 | 'usb', '-U', 'eeprom:r:-:r'] 24 | 25 | # Run the command silently first 26 | proc = subprocess.run(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, 27 | stderr=subprocess.DEVNULL) 28 | 29 | if proc.returncode != 0: 30 | # Make sure the programmer is attached 31 | print(f"Connect the {args.programmer!r} programmer to the computer " 32 | "and to the main microcontroller ISCP port.") 33 | input("Press Enter to continue. ") 34 | 35 | # This time show stderr to the user 36 | proc = subprocess.run(cmd, stdin=subprocess.DEVNULL, 37 | stdout=subprocess.PIPE) 38 | if proc.returncode != 0: 39 | sys.exit(1) 40 | 41 | output = proc.stdout 42 | if len(output) < 1024: 43 | sys.exit(f"Unexpected EEPROM size ({len(output)})") 44 | 45 | for i in range(0, 1024, 4): 46 | value = int.from_bytes(output[i:i + 4], 'little') 47 | if value == 0xFFFFFFFF: 48 | continue 49 | 50 | if value > 100000: 51 | sys.exit(f"Found probably uninitialized value {value:#08x}") 52 | 53 | print(f"{value} resets") 54 | return 55 | 56 | sys.exit("No reset count found (EEPROM was probably erased)") 57 | 58 | if __name__ == '__main__': 59 | run() 60 | --------------------------------------------------------------------------------