init: initial commit
Some checks failed
CI Checks / Building (macOS-latest, stable) (push) Has been cancelled
CI Checks / Building (true, macOS-latest, nightly) (push) Has been cancelled
CI Checks / Building (true, ubuntu-latest, nightly) (push) Has been cancelled
CI Checks / Building (true, windows-latest, nightly) (push) Has been cancelled
CI Checks / Building (ubuntu-latest, stable) (push) Has been cancelled
CI Checks / Building (windows-latest, stable) (push) Has been cancelled
CI Checks / Linting (push) Has been cancelled
CI Checks / Formatting (push) Has been cancelled
Some checks failed
CI Checks / Building (macOS-latest, stable) (push) Has been cancelled
CI Checks / Building (true, macOS-latest, nightly) (push) Has been cancelled
CI Checks / Building (true, ubuntu-latest, nightly) (push) Has been cancelled
CI Checks / Building (true, windows-latest, nightly) (push) Has been cancelled
CI Checks / Building (ubuntu-latest, stable) (push) Has been cancelled
CI Checks / Building (windows-latest, stable) (push) Has been cancelled
CI Checks / Linting (push) Has been cancelled
CI Checks / Formatting (push) Has been cancelled
This commit is contained in:
29
.cargo/config.toml
Normal file
29
.cargo/config.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# Choose a default "cargo run" tool (see README for more info)
|
||||
# - `probe-rs` provides flashing and defmt via a hardware debugger, and stack unwind on panic
|
||||
# - elf2uf2-rs loads firmware over USB when the rp2040 is in boot mode
|
||||
# runner = "probe-rs run --chip RP2040 --protocol swd"
|
||||
# runner = "elf2uf2-rs -d"
|
||||
runner = "picotool load -x -v -t elf"
|
||||
linker = "flip-link"
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-arg=--nmagic",
|
||||
"-C",
|
||||
"link-arg=-Tlink.x",
|
||||
"-C",
|
||||
"link-arg=-Tdefmt.x",
|
||||
|
||||
# Code-size optimizations.
|
||||
# trap unreachable can save a lot of space, but requires nightly compiler.
|
||||
# uncomment the next line if you wish to enable it
|
||||
# "-Z", "trap-unreachable=no",
|
||||
"-C",
|
||||
"no-vectorize-loops",
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi"
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "debug"
|
||||
59
.github/workflows/ci_checks.yml
vendored
Normal file
59
.github/workflows/ci_checks.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: CI Checks
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
building:
|
||||
name: Building
|
||||
continue-on-error: ${{ matrix.experimental || false }}
|
||||
strategy:
|
||||
matrix:
|
||||
# All generated code should be running on stable now
|
||||
rust: [nightly, stable]
|
||||
include:
|
||||
# Nightly is only for reference and allowed to fail
|
||||
- rust: nightly
|
||||
experimental: true
|
||||
os:
|
||||
# Check compilation works on common OSes
|
||||
# (i.e. no path issues)
|
||||
- ubuntu-latest
|
||||
- macOS-latest
|
||||
- windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: thumbv6m-none-eabi
|
||||
- run: cargo install flip-link
|
||||
- run: cargo build --all
|
||||
- run: cargo build --all --release
|
||||
linting:
|
||||
name: Linting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
target: thumbv6m-none-eabi
|
||||
- run: cargo clippy --all-features -- --deny=warnings
|
||||
formatting:
|
||||
name: Formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
target: thumbv6m-none-eabi
|
||||
- run: cargo fmt -- --check
|
||||
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
*.uf2
|
||||
|
||||
**/*.rs.bk
|
||||
.#*
|
||||
.gdb_history
|
||||
Cargo.lock
|
||||
target/
|
||||
|
||||
# editor files
|
||||
.vscode/*
|
||||
!.vscode/*.md
|
||||
!.vscode/*.svd
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rust-analyzer.cargo.target": "thumbv6m-none-eabi",
|
||||
"rust-analyzer.check.allTargets": false,
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
165
COPYING.LESSER
Normal file
165
COPYING.LESSER
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
84
Cargo.toml
Normal file
84
Cargo.toml
Normal file
@@ -0,0 +1,84 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "muse2040"
|
||||
version = "0.1.0"
|
||||
license = "LGPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
embedded-hal = { version = "1.0.0" }
|
||||
|
||||
defmt = "1"
|
||||
defmt-rtt = "1"
|
||||
format_no_std = "1.2.0"
|
||||
heapless = "0.9.2"
|
||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||
portable-atomic = { version = "1.11.1", features = ["critical-section"] }
|
||||
static_cell = "2.1.1"
|
||||
|
||||
rp-pico = "0.9"
|
||||
|
||||
embassy-executor = { version = "0.8.0", features = ["arch-cortex-m", "executor-thread"] }
|
||||
embassy-futures = "0.1.1"
|
||||
embassy-time = { version = "0.4.0" }
|
||||
embassy-rp = { version = "0.7.0", features = ["rp2040", "time-driver", "critical-section-impl"] }
|
||||
embassy-sync = "0.7.1"
|
||||
embassy-usb = "0.5.1"
|
||||
|
||||
cyw43 = { version = "0.4.0", features = ["bluetooth"]}
|
||||
cyw43-firmware = "0.1.0"
|
||||
cyw43-pio = "0.6.0"
|
||||
trouble-host = { version = "0.2.4", features = ["central", "default-packet-pool"]}
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true
|
||||
incremental = false
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
# do not optimize proc-macro crates = faster builds from scratch
|
||||
[profile.dev.build-override]
|
||||
codegen-units = 8
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
opt-level = 0
|
||||
overflow-checks = false
|
||||
|
||||
[profile.release.build-override]
|
||||
codegen-units = 8
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
opt-level = 0
|
||||
overflow-checks = false
|
||||
|
||||
# cargo test
|
||||
[profile.test]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true
|
||||
incremental = false
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
# cargo test --release
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3
|
||||
39
Embed.toml
Normal file
39
Embed.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[default.probe]
|
||||
protocol = "Swd"
|
||||
speed = 20000
|
||||
# If you only have one probe cargo embed will pick automatically
|
||||
# Otherwise: add your probe's VID/PID/serial to filter
|
||||
|
||||
## rust-dap
|
||||
# usb_vid = "6666"
|
||||
# usb_pid = "4444"
|
||||
# serial = "test"
|
||||
|
||||
|
||||
[default.flashing]
|
||||
enabled = true
|
||||
|
||||
[default.reset]
|
||||
enabled = true
|
||||
halt_afterwards = false
|
||||
|
||||
[default.general]
|
||||
chip = "RP2040"
|
||||
log_level = "WARN"
|
||||
# RP2040 does not support connect_under_reset
|
||||
connect_under_reset = false
|
||||
|
||||
[default.rtt]
|
||||
enabled = true
|
||||
up_mode = "NoBlockSkip"
|
||||
channels = [
|
||||
{ up = 0, down = 0, name = "name", up_mode = "NoBlockSkip", format = "Defmt" },
|
||||
]
|
||||
timeout = 3000
|
||||
show_timestamps = true
|
||||
log_enabled = false
|
||||
log_path = "./logs"
|
||||
|
||||
[default.gdb]
|
||||
enabled = false
|
||||
gdb_connection_string = "127.0.0.1:2345"
|
||||
13
IMPLEMENTATION.md
Normal file
13
IMPLEMENTATION.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Implementation
|
||||
|
||||
## Serial Packets
|
||||
|
||||
If you would like to control muse2040 without buttplug.io, you can open a serial connection over USB (Typically `COM3` on Windows and `/dev/ttyACM0` on Linux) and control it directly.
|
||||
|
||||
Since the program takes a long time to control channels (the connection with the toy is 50Hz), the serial connection only allows one channel to change at a time to increase speed.
|
||||
|
||||
Currently, serialising channel and intensity information for muse2040 is done through the following operation:
|
||||
|
||||
```python
|
||||
packet = (channel << 2) + intensity # assuming channel is limited to 0-2 and intensity is limited to 0-3
|
||||
```
|
||||
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# muse2040
|
||||
|
||||
This is a Love Spouse/MuSe sex toy driver using the CYW43 wireless adaptor on the Raspberry Pi Pico W.
|
||||
|
||||
A patch for buttplug.io support can be found in the `patches` directory.
|
||||
|
||||
# Requirements
|
||||
|
||||
- a Raspberry Pi Pico W (unsure about the Pico 2 W)
|
||||
- program requirements listed below
|
||||
- a will to live
|
||||
|
||||
# Installation
|
||||
|
||||
## Binaries
|
||||
|
||||
Since I lack a USB Vendor ID, I cannot distribute the binaries of muse2040 as it uses stock Pi Pico values. I am also most likely not permitted to distribute the patched version of buttplug.io and Intiface, so you'll have to compile that yourself.
|
||||
|
||||
## Compilation
|
||||
|
||||
[Git](https://git-scm.com) is required to clone the Git repositories required and apply the muse2040 patches.
|
||||
[Rust](https://rust-lang.org/learn/get-started/) is required to compile muse2040, buttplug.io and Intiface.
|
||||
[Flutter](https://flutter.dev/) is required to compile Intiface.
|
||||
|
||||
### muse2040
|
||||
|
||||
The `thumbv6m-none-eabi` target is required to build muse2040. You can add it by running the command `rustup target add thumbv6m-none-eabi`.
|
||||
|
||||
Clone the repository with `https://git.girlcock.wang/may/muse2040` and enter it with `cd muse2040`.
|
||||
|
||||
#### Direct Flashing
|
||||
|
||||
You'll need PicoTool installed. You can install it via the package manager of your choice — but since there aren't many distributions of it, you may need to build it from source. **[ [AUR](https://aur.archlinux.org/packages/picotool) | [PicoTool GitHub](https://github.com/raspberrypi/picotool) ]**
|
||||
|
||||
Plug your Pi Pico into your machine via USB while [holding the BOOTSEL button](https://projects-static.raspberrypi.org/projects/getting-started-with-the-pico/f67470613439ea4dc7996f9f4ce83b34c2103fb4/en/images/Pico-bootsel.png). A USB drive should appear called `RPI-RP2`, but disregard it. Run the following command:
|
||||
|
||||
```bash
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
#### UF2 File
|
||||
|
||||
You'll need `elf2uf2-rs` installed. You can install it by running `cargo install elf2uf2-rs`.
|
||||
|
||||
Run the following to build a UF2 of muse2040:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
elf2uf2-rs ./target/thumbv6m-none-eabi/release/muse2040 muse2040.uf2
|
||||
```
|
||||
|
||||
The final UF2 file can be found at the root of the repository.
|
||||
|
||||
## Verifying Installation
|
||||
|
||||
Check that the green LED on the Pico turns solid. Run `lsusb` or check the USB section of Device Manager. You should see a device called `Love Spouse/MuSe Generic Device (muse2040)` with the ID `2e8a:6969`.
|
||||
|
||||
If your toy only uses 1 channel, you can test it by running `python tests/test_vibration.py`. Note that you will need `pyserial` installed for this to work (`pip install pyserial`).
|
||||
|
||||
### buttplug.io + Intiface with muse2040 support
|
||||
|
||||
Go to the parent directory of muse2040 (if you're in the muse2040 directory run `cd ..`) and run the following commands to clone buttplug.io:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/buttplugio/buttplug
|
||||
git clone https://github.com/intiface/intiface-central
|
||||
```
|
||||
|
||||
To patch buttplug.io, run the following:
|
||||
|
||||
```bash
|
||||
cd buttplug
|
||||
git apply ../muse2040/patches/buttplugio.diff
|
||||
cargo build
|
||||
cd ..
|
||||
```
|
||||
|
||||
To compile Intiface, run the following:
|
||||
|
||||
```bash
|
||||
cd intiface-central
|
||||
flutter build linux # you may need to configure JAVA_HOME before executing this!
|
||||
```
|
||||
|
||||
To run the patched Intiface, run `build/linux/x64/release/bundle/intiface_central` in the `intiface-central` directory.
|
||||
|
||||
# Usage with buttplug.io
|
||||
|
||||
Enable **Experimental Settings** in the **App Modes** tab, then scroll down and enable **Serial Port**. Then, go to **Devices** and add a serial device with the following settings:
|
||||
|
||||
| | Linux | Windows |
|
||||
|---------------|--------------|----------|
|
||||
| Protocol Type | muse2040 | muse2040 |
|
||||
| Port Name | /dev/ttyACM0 | COM3 |
|
||||
| Baud Rate | 115200 | 115200 |
|
||||
| Data Bits | 8 | 8 |
|
||||
| Parity | N | N |
|
||||
| Stop Bits | 1 | 1 |
|
||||
|
||||
Turning on the server and starting a scan should (hopefully) show the device.
|
||||
201
THIRDPARTY/rp-rs/LICENSE-APACHE-2.0
Normal file
201
THIRDPARTY/rp-rs/LICENSE-APACHE-2.0
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 rp-rs organization
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
THIRDPARTY/rp-rs/LICENSE-MIT
Normal file
21
THIRDPARTY/rp-rs/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 rp-rs organization
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
build.rs
Normal file
31
build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
BIN
firmware/cyw43/43439A0.bin
Normal file
BIN
firmware/cyw43/43439A0.bin
Normal file
Binary file not shown.
BIN
firmware/cyw43/43439A0_btfw.bin
Normal file
BIN
firmware/cyw43/43439A0_btfw.bin
Normal file
Binary file not shown.
BIN
firmware/cyw43/43439A0_clm.bin
Normal file
BIN
firmware/cyw43/43439A0_clm.bin
Normal file
Binary file not shown.
49
firmware/cyw43/LICENSE-permissive-binary-license-1.0.txt
Normal file
49
firmware/cyw43/LICENSE-permissive-binary-license-1.0.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
Permissive Binary License
|
||||
|
||||
Version 1.0, July 2019
|
||||
|
||||
Redistribution. Redistribution and use in binary form, without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1) Redistributions must reproduce the above copyright notice and the
|
||||
following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
2) Unless to the extent explicitly permitted by law, no reverse
|
||||
engineering, decompilation, or disassembly of this software is
|
||||
permitted.
|
||||
|
||||
3) Redistribution as part of a software development kit must include the
|
||||
accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in
|
||||
that file.
|
||||
|
||||
4) Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
Limited patent license. The copyright holders (and contributors) grant a
|
||||
worldwide, non-exclusive, no-charge, royalty-free patent license to
|
||||
make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer this software, where such license applies only to those patent
|
||||
claims licensable by the copyright holders (and contributors) that are
|
||||
necessarily infringed by this software. This patent license shall not
|
||||
apply to any combinations that include this software. No hardware is
|
||||
licensed hereunder.
|
||||
|
||||
If you institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the software
|
||||
itself infringes your patent(s), then your rights granted under this
|
||||
license shall terminate as of the date such litigation is filed.
|
||||
|
||||
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
14
firmware/cyw43/README.md
Normal file
14
firmware/cyw43/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# WiFi + Bluetooth firmware blobs
|
||||
|
||||
Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware
|
||||
|
||||
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62
|
||||
|
||||
## Notes
|
||||
|
||||
If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM).
|
||||
15
memory.x
Normal file
15
memory.x
Normal file
@@ -0,0 +1,15 @@
|
||||
MEMORY {
|
||||
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
|
||||
EXTERN(BOOT2_FIRMWARE)
|
||||
|
||||
SECTIONS {
|
||||
/* ### Boot loader */
|
||||
.boot2 ORIGIN(BOOT2) :
|
||||
{
|
||||
KEEP(*(.boot2));
|
||||
} > BOOT2
|
||||
} INSERT BEFORE .text;
|
||||
180
patches/buttplugio.diff
Normal file
180
patches/buttplugio.diff
Normal file
@@ -0,0 +1,180 @@
|
||||
diff --git a/crates/buttplug_server/src/device/protocol_impl/mod.rs b/crates/buttplug_server/src/device/protocol_impl/mod.rs
|
||||
index 4e3a275d..677eb8f6 100644
|
||||
--- a/crates/buttplug_server/src/device/protocol_impl/mod.rs
|
||||
+++ b/crates/buttplug_server/src/device/protocol_impl/mod.rs
|
||||
@@ -69,6 +69,7 @@ pub mod mizzzee_v2;
|
||||
pub mod mizzzee_v3;
|
||||
pub mod monsterpub;
|
||||
pub mod motorbunny;
|
||||
+pub mod muse2040;
|
||||
pub mod mysteryvibe;
|
||||
pub mod mysteryvibe_v2;
|
||||
pub mod nextlevelracing;
|
||||
@@ -359,6 +360,10 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
|
||||
&mut map,
|
||||
motorbunny::setup::MotorbunnyIdentifierFactory::default(),
|
||||
);
|
||||
+ add_to_protocol_map(
|
||||
+ &mut map,
|
||||
+ muse2040::setup::MuSe2040IdentifierFactory::default(),
|
||||
+ );
|
||||
add_to_protocol_map(
|
||||
&mut map,
|
||||
mysteryvibe::setup::MysteryVibeIdentifierFactory::default(),
|
||||
diff --git a/crates/buttplug_server/src/device/protocol_impl/muse2040.rs b/crates/buttplug_server/src/device/protocol_impl/muse2040.rs
|
||||
new file mode 100644
|
||||
index 00000000..e267457b
|
||||
--- /dev/null
|
||||
+++ b/crates/buttplug_server/src/device/protocol_impl/muse2040.rs
|
||||
@@ -0,0 +1,39 @@
|
||||
+// Buttplug Rust Source Code File - See https://buttplug.io for more info.
|
||||
+//
|
||||
+// Source protected under Copyright 2025 girlcock (dot) wang. All rights reserved.
|
||||
+//
|
||||
+// Integration is licensed under the BSD 3-Clause license. See LICENSE file in
|
||||
+// the project root for full license information.
|
||||
+//
|
||||
+// Driver (muse2040) is licensed under LGPLv3. See LICENSE file in the muse2040
|
||||
+// project root @ https://git.girlcock.wang/may/muse2040 for full license
|
||||
+// information.
|
||||
+
|
||||
+use crate::device::{
|
||||
+ hardware::{HardwareCommand, HardwareWriteCmd},
|
||||
+ protocol::{ProtocolHandler, generic_protocol_setup},
|
||||
+};
|
||||
+use buttplug_core::errors::ButtplugDeviceError;
|
||||
+use buttplug_server_device_config::Endpoint;
|
||||
+use uuid::Uuid;
|
||||
+
|
||||
+generic_protocol_setup!(MuSe2040, "muse2040");
|
||||
+
|
||||
+#[derive(Default)]
|
||||
+pub struct MuSe2040 {}
|
||||
+
|
||||
+impl ProtocolHandler for MuSe2040 {
|
||||
+ fn handle_output_vibrate_cmd(
|
||||
+ &self,
|
||||
+ _feature_index: u32,
|
||||
+ feature_id: Uuid,
|
||||
+ speed: u32,
|
||||
+ ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
|
||||
+
|
||||
+ let data = ((_feature_index << 2) + speed) as u8;
|
||||
+
|
||||
+ Ok(vec![
|
||||
+ HardwareWriteCmd::new(&[feature_id], Endpoint::Tx, vec![data], true).into(),
|
||||
+ ])
|
||||
+ }
|
||||
+}
|
||||
diff --git a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json
|
||||
index b4f91519..95a2e108 100644
|
||||
--- a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json
|
||||
+++ b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json
|
||||
@@ -13321,6 +13321,61 @@
|
||||
"name": "Muse Device"
|
||||
}
|
||||
},
|
||||
+ "muse2040": {
|
||||
+ "communication": [
|
||||
+ {
|
||||
+ "serial": {
|
||||
+ "baud_rate": 115200,
|
||||
+ "data_bits": 8,
|
||||
+ "parity": "N",
|
||||
+ "port": "default",
|
||||
+ "stop_bits": 1
|
||||
+ }
|
||||
+ }
|
||||
+ ],
|
||||
+ "defaults": {
|
||||
+ "features": [
|
||||
+ {
|
||||
+ "description": "Channel 0 (single-channel devices)",
|
||||
+ "id": "e9594130-280a-4ec0-aff9-c799f35321e6",
|
||||
+ "output": {
|
||||
+ "vibrate": {
|
||||
+ "value": [
|
||||
+ 0,
|
||||
+ 3
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "description": "Channel 1",
|
||||
+ "id": "12c1a484-8aae-48e2-a041-099d59f86ef6",
|
||||
+ "output": {
|
||||
+ "vibrate": {
|
||||
+ "value": [
|
||||
+ 0,
|
||||
+ 3
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "description": "Channel 2",
|
||||
+ "id": "4d541da0-f607-43b4-a4ab-90e52c3677c3",
|
||||
+ "output": {
|
||||
+ "vibrate": {
|
||||
+ "value": [
|
||||
+ 0,
|
||||
+ 3
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ ],
|
||||
+ "id": "eed7545b-3016-494a-b611-77941455dfed",
|
||||
+ "name": "Love Spouse/MuSe Device (muse2040)"
|
||||
+ }
|
||||
+ },
|
||||
"mysteryvibe": {
|
||||
"communication": [
|
||||
{
|
||||
diff --git a/crates/buttplug_server_device_config/device-config-v4/protocols/muse2040.yml b/crates/buttplug_server_device_config/device-config-v4/protocols/muse2040.yml
|
||||
new file mode 100644
|
||||
index 00000000..1fc4523e
|
||||
--- /dev/null
|
||||
+++ b/crates/buttplug_server_device_config/device-config-v4/protocols/muse2040.yml
|
||||
@@ -0,0 +1,39 @@
|
||||
+# Integration is licensed under the BSD 3-Clause license. See LICENSE file in
|
||||
+# the project root for full license information.
|
||||
+#
|
||||
+# Driver (muse2040) is licensed under LGPLv3. See LICENSE file in the muse2040
|
||||
+# project root @ https://git.girlcock.wang/may/muse2040 for full license
|
||||
+# information.
|
||||
+
|
||||
+defaults:
|
||||
+ name: Love Spouse/MuSe Device (muse2040)
|
||||
+ features:
|
||||
+ - description: Channel 0 (single-channel devices)
|
||||
+ id: e9594130-280a-4ec0-aff9-c799f35321e6
|
||||
+ output:
|
||||
+ vibrate:
|
||||
+ value:
|
||||
+ - 0
|
||||
+ - 3
|
||||
+ - description: Channel 1
|
||||
+ id: 12c1a484-8aae-48e2-a041-099d59f86ef6
|
||||
+ output:
|
||||
+ vibrate:
|
||||
+ value:
|
||||
+ - 0
|
||||
+ - 3
|
||||
+ - description: Channel 2
|
||||
+ id: 4d541da0-f607-43b4-a4ab-90e52c3677c3
|
||||
+ output:
|
||||
+ vibrate:
|
||||
+ value:
|
||||
+ - 0
|
||||
+ - 3
|
||||
+ id: eed7545b-3016-494a-b611-77941455dfed
|
||||
+communication:
|
||||
+ - serial:
|
||||
+ port: default
|
||||
+ baud_rate: 115200
|
||||
+ data_bits: 8
|
||||
+ parity: 'N'
|
||||
+ stop_bits: 1
|
||||
\ No newline at end of file
|
||||
36
src/advert.rs
Normal file
36
src/advert.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2025 girlcock (dot) wang
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use trouble_host::prelude::{AdStructure, LE_GENERAL_DISCOVERABLE};
|
||||
|
||||
use crate::packets::MuSePacket;
|
||||
|
||||
pub fn build_packet(command: MuSePacket) -> ([u8; 64], usize) {
|
||||
let mut adv_data = [0; 64];
|
||||
|
||||
let len = AdStructure::encode_slice(
|
||||
&[
|
||||
AdStructure::Flags(LE_GENERAL_DISCOVERABLE),
|
||||
AdStructure::ManufacturerSpecificData {
|
||||
company_identifier: 0xFFF0,
|
||||
payload: &command,
|
||||
},
|
||||
],
|
||||
&mut adv_data[..],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
return (adv_data, len);
|
||||
}
|
||||
21
src/macros.rs
Normal file
21
src/macros.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2025 girlcock (dot) wang
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! muse_cmd {
|
||||
($a:expr, $b:expr, $c:expr) => {
|
||||
[0x6D, 0xB6, 0x43, 0xCE, 0x97, 0xFE, 0x42, 0x7C, $a, $b, $c]
|
||||
};
|
||||
}
|
||||
237
src/main.rs
Normal file
237
src/main.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright (C) 2025 girlcock (dot) wang
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER};
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::join as future_join;
|
||||
use embassy_rp::{
|
||||
bind_interrupts,
|
||||
gpio::{Level, Output},
|
||||
peripherals::{DMA_CH0, PIO0, USB},
|
||||
pio::{InterruptHandler as PIOInterruptHandler, Pio},
|
||||
usb::{Driver, InterruptHandler as USBInterruptHandler},
|
||||
};
|
||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embassy_usb::{self, class::cdc_acm::CdcAcmClass, UsbDevice};
|
||||
// use heapless::String;
|
||||
use static_cell::StaticCell;
|
||||
use trouble_host::{
|
||||
prelude::{AdvertisementParameters, DefaultPacketPool, ExternalController},
|
||||
Address, Host, HostResources,
|
||||
};
|
||||
|
||||
use embassy_usb::class::cdc_acm::State as USBState;
|
||||
|
||||
use crate::{advert::build_packet, packets::*};
|
||||
|
||||
use {defmt_rtt as _, embassy_time as _, panic_probe as _};
|
||||
|
||||
mod advert;
|
||||
mod macros;
|
||||
mod packets;
|
||||
|
||||
type USBDriver = Driver<'static, USB>;
|
||||
type USBDevice = UsbDevice<'static, USBDriver>;
|
||||
|
||||
bind_interrupts!(
|
||||
struct Irqs {
|
||||
PIO0_IRQ_0 => PIOInterruptHandler<PIO0>;
|
||||
USBCTRL_IRQ => USBInterruptHandler<USB>;
|
||||
}
|
||||
);
|
||||
|
||||
// dummy task to run the cyw event handler
|
||||
#[embassy_executor::task]
|
||||
async fn cyw43_task(
|
||||
runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>,
|
||||
) -> ! {
|
||||
runner.run().await
|
||||
}
|
||||
|
||||
// dummy task to run the USB stack
|
||||
#[embassy_executor::task]
|
||||
async fn usb_task(mut usb: USBDevice) -> ! {
|
||||
usb.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
// set up USB serial
|
||||
let usb_driver = Driver::new(p.USB, Irqs);
|
||||
|
||||
let usb_config = {
|
||||
let mut config = embassy_usb::Config::new(0x2E8A, 0x6969); // ! DEVELOPMENT ONLY! change the VID/PID if binaries are being distributed!
|
||||
config.manufacturer = Some("Love Spouse/MuSe");
|
||||
config.product = Some("Generic Device (muse2040)");
|
||||
config.serial_number = Some("2040990000");
|
||||
config.max_power = 100;
|
||||
config.max_packet_size_0 = 64;
|
||||
config
|
||||
};
|
||||
|
||||
let mut usb_builder = {
|
||||
static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
|
||||
static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
|
||||
static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
|
||||
|
||||
let builder = embassy_usb::Builder::new(
|
||||
usb_driver,
|
||||
usb_config,
|
||||
CONFIG_DESCRIPTOR.init([0; 256]),
|
||||
BOS_DESCRIPTOR.init([0; 256]),
|
||||
&mut [], // no msos descriptors
|
||||
CONTROL_BUF.init([0; 64]),
|
||||
);
|
||||
builder
|
||||
};
|
||||
|
||||
let mut usb_class = {
|
||||
static STATE: StaticCell<USBState> = StaticCell::new();
|
||||
let state = STATE.init(USBState::new());
|
||||
CdcAcmClass::new(&mut usb_builder, state, 64)
|
||||
};
|
||||
|
||||
let usb = usb_builder.build();
|
||||
|
||||
unwrap!(spawner.spawn(usb_task(usb)), "usb task spawn failed");
|
||||
|
||||
// ~ INCLUDE FIRMWARE ~
|
||||
let fw = include_bytes!("../firmware/cyw43/43439A0.bin");
|
||||
let clm = include_bytes!("../firmware/cyw43/43439A0_clm.bin");
|
||||
let btfw = include_bytes!("../firmware/cyw43/43439A0_btfw.bin");
|
||||
|
||||
// initialise cyw43 spi interface
|
||||
let pwr = Output::new(p.PIN_23, Level::Low);
|
||||
let cs = Output::new(p.PIN_25, Level::High);
|
||||
let mut pio = Pio::new(p.PIO0, Irqs);
|
||||
let spi = PioSpi::new(
|
||||
&mut pio.common,
|
||||
pio.sm0,
|
||||
DEFAULT_CLOCK_DIVIDER,
|
||||
pio.irq0,
|
||||
cs,
|
||||
p.PIN_24,
|
||||
p.PIN_29,
|
||||
p.DMA_CH0,
|
||||
);
|
||||
|
||||
static STATE: StaticCell<cyw43::State> = StaticCell::new();
|
||||
let state: &'static mut cyw43::State = STATE.init(cyw43::State::new());
|
||||
|
||||
// initialise the bluetooth card
|
||||
let (_net_device, bt_device, mut control, runner) =
|
||||
cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await;
|
||||
unwrap!(spawner.spawn(cyw43_task(runner)), "spawn failed");
|
||||
control.init(clm).await;
|
||||
|
||||
let controller: ExternalController<_, 10> = ExternalController::new(bt_device);
|
||||
|
||||
let address = Address::random([0x0, 0x50, 0x1e, 0x0b, 0xb0, 0x00]); // i have to put a null byte at the end (well, start since it's little endian) for some reason
|
||||
info!("[stack] ble address: {:?}", Debug2Format(&address));
|
||||
|
||||
// build a BLE stack
|
||||
let mut resources: HostResources<DefaultPacketPool, 0, 0, 27> = HostResources::new();
|
||||
|
||||
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
|
||||
|
||||
let Host {
|
||||
mut peripheral,
|
||||
mut runner,
|
||||
..
|
||||
} = stack.build();
|
||||
|
||||
info!("[stack] built stack");
|
||||
|
||||
let mut params = AdvertisementParameters::default();
|
||||
params.interval_min = Duration::from_millis(20); // 50Hz
|
||||
params.interval_max = Duration::from_millis(20);
|
||||
|
||||
control.gpio_set(0, true).await; // turn on to indicate the driver has initialised
|
||||
|
||||
let channel: Channel<NoopRawMutex, u8, 1> = Channel::new();
|
||||
|
||||
let _ = future_join::join3(
|
||||
runner.run(),
|
||||
async {
|
||||
let (adv, adv_len) = build_packet(C0S0); // initial state
|
||||
|
||||
let adata = trouble_host::prelude::Advertisement::ConnectableScannableUndirected {
|
||||
adv_data: &adv[..adv_len],
|
||||
scan_data: &[],
|
||||
};
|
||||
|
||||
let _advertiser = peripheral.advertise(¶ms, adata).await.unwrap();
|
||||
|
||||
loop {
|
||||
let bit_state = channel.receive().await;
|
||||
|
||||
control.gpio_set(0, false).await;
|
||||
|
||||
// the intensity is stored as the last two bits of the byte, with the channel being the fifth and sixth bits.
|
||||
let channel = (bit_state >> 2 & 0b11) as usize;
|
||||
let intensity = (bit_state >> 0 & 0b11) as usize;
|
||||
|
||||
let (adv, adv_len) = build_packet(LUT[channel][intensity]);
|
||||
|
||||
peripheral
|
||||
.update_adv_data(
|
||||
trouble_host::prelude::Advertisement::ConnectableScannableUndirected {
|
||||
adv_data: &adv[..adv_len],
|
||||
scan_data: &[],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Timer::after(Duration::from_millis(20)).await; // ! potential race condition? the bt driver advertisement timer and this run on two different clocks...
|
||||
|
||||
control.gpio_set(0, true).await;
|
||||
}
|
||||
},
|
||||
async {
|
||||
loop {
|
||||
usb_class.wait_connection().await;
|
||||
|
||||
let mut buf = [0; 1];
|
||||
|
||||
loop {
|
||||
let _len = unwrap!(
|
||||
usb_class.read_packet(&mut buf).await,
|
||||
"could not read packet"
|
||||
);
|
||||
|
||||
channel.send(buf[0]).await;
|
||||
|
||||
// // * just a shitty failsafe to get data through serial
|
||||
|
||||
// let mut buffer: String<64> = String::new();
|
||||
// core::fmt::write(&mut buffer, format_args!("data: {}", buf[0]));
|
||||
|
||||
// usb_class.write_packet(buffer.as_bytes()).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// End of file
|
||||
44
src/packets.rs
Normal file
44
src/packets.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2025 girlcock (dot) wang
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::muse_cmd;
|
||||
|
||||
pub type MuSePacket = [u8; 11];
|
||||
|
||||
// C<channel>S<speed>
|
||||
|
||||
// 0 - "all" channels
|
||||
pub const C0S0: MuSePacket = muse_cmd!(0xE5, 0x15, 0x7D);
|
||||
pub const C0S1: MuSePacket = muse_cmd!(0xE4, 0x9C, 0x6C);
|
||||
pub const C0S2: MuSePacket = muse_cmd!(0xE7, 0x07, 0x5E);
|
||||
pub const C0S3: MuSePacket = muse_cmd!(0xE6, 0x8E, 0x4F);
|
||||
|
||||
// 1 - Channel 1
|
||||
pub const C1S0: MuSePacket = muse_cmd!(0xD5, 0x96, 0x4C);
|
||||
pub const C1S1: MuSePacket = muse_cmd!(0xD4, 0x1F, 0x5D);
|
||||
pub const C1S2: MuSePacket = muse_cmd!(0xD7, 0x84, 0x6F);
|
||||
pub const C1S3: MuSePacket = muse_cmd!(0xD6, 0x0D, 0x7E);
|
||||
|
||||
// 2 - Channel 2
|
||||
pub const C2S0: MuSePacket = muse_cmd!(0xA5, 0x11, 0x3F);
|
||||
pub const C2S1: MuSePacket = muse_cmd!(0xA4, 0x98, 0x2E);
|
||||
pub const C2S2: MuSePacket = muse_cmd!(0xA7, 0x03, 0x1C);
|
||||
pub const C2S3: MuSePacket = muse_cmd!(0xA6, 0x8A, 0x0D);
|
||||
|
||||
pub const LUT: [[MuSePacket; 4]; 3] = [
|
||||
[C0S0, C0S1, C0S2, C0S3],
|
||||
[C1S0, C1S1, C1S2, C1S3],
|
||||
[C2S0, C2S1, C2S2, C2S3],
|
||||
];
|
||||
22
tests/test_vibration.py
Normal file
22
tests/test_vibration.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import serial
|
||||
import struct
|
||||
import time
|
||||
|
||||
conn = serial.Serial("COM3" if os.name == "nt" else "/dev/ttyACM0")
|
||||
|
||||
def change(channel, intensity):
|
||||
return struct.pack("B", (channel << 2) + intensity)
|
||||
|
||||
try:
|
||||
print("Blinking (1sec intervals)... [Press ^C to exit]\x1b[?25l")
|
||||
while True:
|
||||
print("\r\x1b[32m- vibrating -\x1b[0m", end="", flush=True)
|
||||
conn.write(change(0, 1))
|
||||
time.sleep(1)
|
||||
print("\r\x1b[35m- sleeping - \x1b[0m", end="", flush=True)
|
||||
conn.write(change(0, 0))
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
conn.write(change(0, 0))
|
||||
print("\x1b[?25h")
|
||||
Reference in New Issue
Block a user