Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/semantic-release.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Release

on:
push:
branches:
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Go CI

on:
push:
branches:
- "**"
pull_request:
branches:
- "**"

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.26" # Specify your desired Go version

- name: Update package source
run: sudo apt-get update

- name: Install systemd-devel
run: sudo apt-get install libudev-dev

- name: Download dependencies
run: go mod download

- name: Build
run: go build -v ./...

- name: Test with the Go CLI
run: go test -v ./...

- name: Generate coverage report
run: go test -coverprofile=coverage.out $(go list ./... | grep -v /examples/)

- name: Check coverage
id: coverage
run: |
coverage=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
echo "total_coverage=$coverage" >> $GITHUB_OUTPUT
echo "Coverage: $coverage%"

- name: Fail if coverage is below threshold
run: |
total_coverage="${{ steps.coverage.outputs.total_coverage }}"
if (( $(echo "$total_coverage < 80" | bc -l) )); then
echo "Coverage ($total_coverage%) is below the threshold (80%)"
exit 1
fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__debug_bin*
*.exe
node_modules
coverage.out
9 changes: 9 additions & 0 deletions .qwen/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(go *)",
"Bash(sed *)"
]
},
"$version": 3
}
7 changes: 7 additions & 0 deletions .qwen/settings.json.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(go *)"
]
}
}
65 changes: 38 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ View docs on pkg.go.dev:

To import as a dependency:

```
```go
go get github.com/JamesBalazs/speed-editor-client
```

The project depends on the [go-hid](https://github.com/sstallion/go-hid) library.

Before creating a Speed Editor client, we need to initialize the HID library:

```
```go
if err := hid.Init(); err != nil {
log.Fatal(err)
}
Expand All @@ -47,15 +47,18 @@ Don't forget to defer a call to `Exit` to avoid memory leaks.

Next we can initialize the client:

```
client := speedEditor.NewClient()
```go
client, err := speedEditor.NewClient()
if err != nil {
log.Fatal(err)
}
```

This connects to the Speed Editor, requests the manufacturer info, and device info such as serial number, and sets up the default event handlers.

Device info is cached on initialize, since it will never change once the device is connected:=

```
```go
deviceInfo := client.GetDeviceInfo()

fmt.Printf("Manufacturer: %s\nProduct: %s\nSerial: %s\n", deviceInfo.MfrStr, deviceInfo.ProductStr, deviceInfo.SerialNbr)
Expand All @@ -72,21 +75,23 @@ I re-implemented his authentication algorithm in Go, and exported the underlying

When using the client, you just need to call `Authenticate` before sending / receiving any messages, and the handshake will be handled for you:

```
client.Authenticate()
```go
if err := client.Authenticate(); err != nil {
log.Fatal(err)
}
```

Finally, to receive messages from the Speed Editor, you can call `Poll`. This will start a loop which does a blocking read, waiting for either a keypress, battery report, or jog wheel movement from the device:

```
```go
client.Poll()
```

When any of the aforementioned events happen, the corresponding Handler function is called.

The event handlers can be overridden by the user to implement custom functionality:

```
```go
func customJogHandler(client speedEditor.SpeedEditorInt, report input.JogReport) {
fmt.Printf("Jog wheel position: %d\n", report.Value)
}
Expand All @@ -112,14 +117,18 @@ This helps light LEDs based on their position such as in the lightshow and volum

You can light any combination of LEDs on the board:

```
```go
keysByName := keys.ByName()

leds := []uint32{keysByName[keys.CAM7.Led], keysByName[keys.CAM5.Led], keysByName[keys.CAM3.Led]}
jogLeds := []uint8{keysByName[keys.SHTL.JogLed], keysByName[keys.JOG.JogLed], keysByName[keys.SCROLL.JogLed]}

client.SetLeds(leds)
client.SetJogLeds(jogLeds)
if err := client.SetLeds(leds); err != nil {
log.Fatal(err)
}
if err := client.SetJogLeds(jogLeds); err != nil {
log.Fatal(err)
}
```

`JOG`/`SCRL`/`SHTL` are on a different system to the other LEDs, so a different function is required to light them.
Expand All @@ -137,21 +146,23 @@ Finally, there are a few different jog modes available on the device:

You can switch modes via the client:

```
client.SetJogMode(jogModes.ABSOLUTE)
```go
if err := client.SetJogMode(jogModes.ABSOLUTE); err != nil {
log.Fatal(err)
}
```

You will have to handle lighting the buttons yourself, if you want the modes to work like they do with the editor connected to Davinci.

You can also get a list of keys and their attributes, with deterministic ordering:

```
```go
keys.Get()
```

And maps / indexes of keys by name, ID, LED ID etc so you can easily retrieve key details given only a single attribute, in constant time:

```
```go
keys.ById()
keys.ByName()
keys.ByLedId()
Expand All @@ -164,7 +175,7 @@ keys.ByRow()

The same goes for jog modes:

```
```go
jogModes.Get()

jogModes.ById()
Expand All @@ -180,44 +191,44 @@ This is done since manipulating the maps could mess up the underlying key data i
My setup is weird (WSL remote via Zed) so some extra steps are required to pass the Speed Editor through to WSL

Installing [usbipd](https://github.com/dorssel/usbipd-win):
```
```bash
winget install usbipd
```

Listing devices:
```
```bash
usbipd list
```

Binding the Speed Editor (persists reboot, your BUSID will be different to mine):
```
```bash
sudo usbipd bind --busid=4-9
```

Attaching to WSL (does not persist reboot):
```
```bash
sudo usbipd attach --wsl --busid=4-9
```

To confirm w/ [lshid](https://github.com/FFY00/lshid) within WSL:
```
```bash
$HOME/go/bin/lshid
```
Should output something like `/dev/hidraw0: ID 1edb:da0e Blackmagic Design DaVinci Resolve Speed Editor`

### Deps

```
```bash
sudo dnf install systemd-devel
```
To get `libudev.h` on Fedora (required for lshid)

I then had permission issues reading from `/dev/hidraw0` so had to create a [udev](https://wiki.archlinux.org/title/Udev) rule:
```
```bash
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0660", GROUP="plugdev"
```
in `/etc/udev/rules.d/99-hidraw-permissions.rules`, then:
```
```bash
sudo groupadd plugdev
sudo usermod -a -G plugdev james
sudo udevadm control --reload
Expand All @@ -229,12 +240,12 @@ After this `stat /dev/hidraw0` should list the new plugdev group.
### Cross platform builds for Windows

mingw-w64 is required to compile the HID library on Linux for Windows with CGO. Installation:
```
```bash
sudo dnf install mingw64-gcc
```

To build the examples:
```
```bash
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc go build main.go
```

Expand Down
Loading
Loading