From 5233111f257ec4832ac418f0be93f096c93208dd Mon Sep 17 00:00:00 2001 From: "Riley L." Date: Sat, 30 Mar 2024 18:31:00 +0100 Subject: [PATCH] arson on a massive scale --- cmd/log/log.go | 35 ++++ cmd/obsconnect/config.yml | 9 + cmd/obsconnect/obs.go | 343 ++++++++++++++++++++++++++++++++++ go.mod | 19 ++ go.sum | 28 +++ mixer.go | 378 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 812 insertions(+) create mode 100644 cmd/log/log.go create mode 100644 cmd/obsconnect/config.yml create mode 100644 cmd/obsconnect/obs.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 mixer.go diff --git a/cmd/log/log.go b/cmd/log/log.go new file mode 100644 index 0000000..70060b8 --- /dev/null +++ b/cmd/log/log.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/rileys-trash-can/newtecrs82obs" + "log" + "os" +) + +func main() { + if len(os.Args) < 2 { + log.Fatalf("Usage: log ") + } + + port := os.Args[1] + conn, err := nt8.Open(port) + if err != nil { + log.Fatalf("Failed to connect: %s", err) + } + + conn.InitBlynk() + + ch := conn.ReadCh() + + for { + e := <-ch + + switch event := e.(type) { + case *nt8.EventButton: + log.Printf("%T %#v", event, event) + + case *nt8.EventSlider: + log.Printf("%X %3d", event.Type, event.Value) + } + } +} diff --git a/cmd/obsconnect/config.yml b/cmd/obsconnect/config.yml new file mode 100644 index 0000000..033ac74 --- /dev/null +++ b/cmd/obsconnect/config.yml @@ -0,0 +1,9 @@ +serial.port: "/dev/ttyUSB0" + +boolshit: true + +obs.addr: "10.10.42.160:4444" +obs.pass: "asdfgh" + +trans.auto: "Fade" +trans.take: "Cut" diff --git a/cmd/obsconnect/obs.go b/cmd/obsconnect/obs.go new file mode 100644 index 0000000..9d20719 --- /dev/null +++ b/cmd/obsconnect/obs.go @@ -0,0 +1,343 @@ +package main + +import ( + obs "github.com/andreykaipov/goobs" + obse "github.com/andreykaipov/goobs/api/events" + obss "github.com/andreykaipov/goobs/api/events/subscriptions" + obssc "github.com/andreykaipov/goobs/api/requests/scenes" + obst "github.com/andreykaipov/goobs/api/requests/transitions" + "github.com/rileys-trash-can/newtecrs82obs" + "gopkg.in/yaml.v3" + "log" + "math/rand" + "os" + "os/exec" + "time" +) + +var Config config + +type config struct { + Serial string `yaml:"serial.port"` + + Boolshit bool `yaml:"boolshit"` // enables disables bullshit + + OBSAddr string `yaml:"obs.addr"` // ip with port + OBSPass string `yaml:"obs.pass"` // password + + TransAuto string `yaml:"trans.auto"` // transition used for auto button + TransTake string `yaml:"trans.take"` // transition used for take button +} + +func readconfig() { + f, err := os.OpenFile("config.yml", os.O_RDONLY, 0755) + if err != nil { + log.Fatalf("Failed to open config: %s", err) + } + + defer f.Close() + + dec := yaml.NewDecoder(f) + err = dec.Decode(&Config) + if err != nil { + log.Fatalf("Failed to decode config: %s", err) + } +} + +func main() { + log.SetFlags(log.Flags() | log.Lshortfile) + + readconfig() + + port := Config.Serial + conn, err := nt8.Open(port) + if err != nil { + log.Fatalf("Failed to connect: %s", err) + } + + // conn.InitBlynk() + + ch := conn.ReadCh() + + obsc, err := obs.New(Config.OBSAddr, + obs.WithPassword(Config.OBSPass), + obs.WithEventSubscriptions(obss.Scenes), + ) + if err != nil { + log.Fatalf("Failed to connect: %s", err) + } + + t, err := obsc.Transitions.GetTransitionKindList() + if err != nil { + log.Fatalf("Failed to connect gtkl: %s", err) + } + + for _, name := range t.TransitionKinds { + log.Printf(" > %s", name) + } + + sl, err := obsc.Scenes.GetSceneList() + if err != nil { + log.Fatalf("Failed to gsl: %s", err) + } + + sceneindex := make([]string, 8) + scenenamemap := make(map[string]int) + + lim := lower(len(sl.Scenes), 8) + for i := 0; i < lim; i++ { + sceneindex[i] = sl.Scenes[lim-i-1].SceneName + scenenamemap[sl.Scenes[lim-i-1].SceneName] = i + } + + for k, v := range sceneindex { + log.Printf("%d %v", k, v) + } + log.Printf("scenemap: %+#v", scenenamemap) + + const ( + TBAR uint8 = iota + ) + + ticker := time.NewTicker(time.Second / 10) + changed := make(map[uint8]struct{}) + debounce := make(map[uint16]time.Time) + var tbarbos float64 + + deb := func(t uint16) bool { + now := time.Now() + + last, ok := debounce[t] + if !ok { + debounce[t] = now + + return false + } + + if last.Before(now.Add(-time.Millisecond * 100)) { + debounce[t] = now + + return false + } + return true + } + + go obsc.Listen(func(l any) { + switch event := l.(type) { + case *obse.CurrentPreviewSceneChanged: + log.Printf("current preview scene: %s %d", event.SceneName, scenenamemap[event.SceneName]+1) + program := make([]nt8.CmdLight, 0) + + for i := uint8(0); i < 8; i++ { + program = append(program, nt8.CmdLight{ + Type: nt8.ButtonPreview, + Value: i, + State: nt8.LightOff, + }) + } + + program[7-scenenamemap[event.SceneName]].State = nt8.LightOn + + conn.LightCmdCh <- program + break + + case *obse.CurrentProgramSceneChanged: + log.Printf("current program scene: %s %d", event.SceneName, scenenamemap[event.SceneName]+1) + program := make([]nt8.CmdLight, 0) + + for i := uint8(0); i < 8; i++ { + program = append(program, nt8.CmdLight{ + Type: nt8.ButtonProgram, + Value: i, + State: nt8.LightOff, + }) + } + + program[7-scenenamemap[event.SceneName]].State = nt8.LightOn + + conn.LightCmdCh <- program + break + + default: + //log.Printf("non handled thingy %T", event) + break + } + }) + + for { + select { + case <-ticker.C: + if _, ok := changed[TBAR]; ok { + _, err = obsc.Transitions.SetTBarPosition(&obst.SetTBarPositionParams{ + Position: i(tbarbos), + Release: i(tbarbos == 0 || tbarbos == 1), + }) + if err != nil { + log.Printf("Error setting tbar: %s", err) + } + + } + + changed = make(map[uint8]struct{}) + + case e := <-ch: + switch event := e.(type) { + case *nt8.EventButton: + if event.Direction != 0 { + continue + } + + switch event.Type { + case nt8.ButtonProgram: + log.Printf("Setting Program to %d", 7-event.Value) + obsc.Scenes.SetCurrentProgramScene(&obssc.SetCurrentProgramSceneParams{ + SceneName: &sceneindex[7-event.Value], + }) + + break + case nt8.ButtonPreview: + log.Printf("Setting Program to %d", 7-event.Value) + obsc.Scenes.SetCurrentPreviewScene(&obssc.SetCurrentPreviewSceneParams{ + SceneName: &sceneindex[7-event.Value], + }) + + break + case nt8.ButtonAutoTakeDSK: + switch event.Value { + case 0x01: // TAKE or 0x02 + if deb(nt8.ButtonAutoTakeDSK<<8 | 0x01) { + continue + } + + go func() { + oldprog, err := obsc.Scenes.GetCurrentProgramScene() + if err != nil { + log.Printf("oldprogram: %s (uuid %s)", oldprog.SceneName, oldprog.SceneUuid) + } + + _, err = obsc.Transitions.SetCurrentSceneTransition(&obst.SetCurrentSceneTransitionParams{ + TransitionName: &Config.TransTake, + }) + if err != nil { + log.Printf("set transition: %s", err) + } + + _, err = obsc.Transitions.TriggerStudioModeTransition() + if err != nil { + log.Printf("Studiomodetransition: %s", err) + } + + _, err = obsc.Scenes.SetCurrentPreviewScene(&obssc.SetCurrentPreviewSceneParams{ + SceneUuid: &oldprog.SceneUuid, + }) + if err != nil { + log.Printf("set current preview: %s", err) + } + }() + case 0x03: // AUTO + + go func() { + oldprog, err := obsc.Scenes.GetCurrentProgramScene() + if err != nil { + log.Printf("oldprogram: %s (uuid %s)", oldprog.SceneName, oldprog.SceneUuid) + } + + _, err = obsc.Transitions.SetCurrentSceneTransition(&obst.SetCurrentSceneTransitionParams{ + TransitionName: &Config.TransAuto, + }) + if err != nil { + log.Printf("set transition: %s", err) + } + + _, err = obsc.Transitions.TriggerStudioModeTransition() + if err != nil { + log.Printf("Studiomodetransition: %s", err) + } + + _, err = obsc.Scenes.SetCurrentPreviewScene(&obssc.SetCurrentPreviewSceneParams{ + SceneUuid: &oldprog.SceneUuid, + }) + if err != nil { + log.Printf("set current preview: %s", err) + } + }() + if Config.Boolshit { + go exec.Command("gti").Run() + } + + case 0x05: // DDR + if Config.Boolshit { + println(" ____ ____ ____\n| _ \\| _ \\| _ \\\n| | | | | | | |_) |\n| |_| | |_| | _ <\n|____/|____/|_| \\_\\") + println(ddrquote()) + } + } + } + break + + case *nt8.EventSlider: + if event.Type == nt8.SliderTbar { + value := (float64(event.Value)) / 250 + if value > 1 { + value = 1 + + } + + if value < 0 { + value = 0 + } + + log.Printf("tbar %1.3f", value) + + tbarbos = value + changed[TBAR] = struct{}{} + continue + } + + log.Printf("%X %3d", event.Type, event.Value) + + } + } + } + + obsc.Disconnect() +} + +func i[K any](a K) *K { + return &a +} + +func lower(a, b int) int { + if a > b { + return b + } + + return a +} + +var ddrquotes = []string{ + "Niemand hat die Absicht eine Mauer zu bauen", + "Den Sozialismus in seinem Lauf, hält weder Ochs noch Esel auf.", + "Frieden ist nicht alles, aber ohne Frieden ist alles nichts.", + "Wir müssen lernen, wie die Gesellschaft so zu gestalten, dass die Menschen glücklich sind.", + "Die Partei hat immer recht.", + "Vorwärts immer, rückwärts nimmer!", + "Den Sozialismus in seinem Lauf, hält weder Ochs noch Esel auf.", + "Frieden ist nicht alles, aber ohne Frieden ist alles nichts.", + "Wir müssen lernen, wie die Gesellschaft so zu gestalten, dass die Menschen glücklich sind.", + "Die Partei hat immer recht.", + "Vorwärts immer, rückwärts nimmer!", + "Wer kämpft, kann verlieren. Wer nicht kämpft, hat schon verloren.", + "Es muss demokratisiert werden im tiefsten Sinne des Wortes, die Wirtschaft, die Wissenschaft, das kulturelle Leben.", + "Freundschaft, das ist das schönste auf der Welt.", + "Niemand hat die Absicht, eine Mauer zu errichten.", + "Die Wahrheit ist, dass wir noch nicht da sind, wo wir sein sollten.", + "Die Zukunft gehört dem Sozialismus.", + "Der Weg der sozialistischen Partei ist der Weg des Volkes.", + "Wir haben das Glück, in einer Zeit zu leben, in der wir Zeugen eines großen historischen Wandels sind.", + "Sozialismus ist das Gegenteil von Egoismus.", + "Die Kunst ist eine Waffe der Revolution.", +} + +func ddrquote() string { + return ddrquotes[rand.Intn(len(ddrquotes))] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9b2afb5 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/rileys-trash-can/newtecrs82obs + +go 1.22.1 + +require ( + github.com/andreykaipov/goobs v1.2.3 + github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/buger/jsonparser v1.1.1 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/mmcloughlin/profile v0.1.1 // indirect + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..38c1cd0 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/andreykaipov/goobs v1.2.3 h1:zqqr8mYwPNLHmoVMRPAJ7tcafO/CR7xua7rPoDMpDZ8= +github.com/andreykaipov/goobs v1.2.3/go.mod h1:D7+36C+8xY/aCHhwWZlyHbqApAfGPc3mFN3uhUjjnk0= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/mmcloughlin/profile v0.1.1 h1:jhDmAqPyebOsVDOCICJoINoLb/AnLBaUw58nFzxWS2w= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mixer.go b/mixer.go new file mode 100644 index 0000000..22e6562 --- /dev/null +++ b/mixer.go @@ -0,0 +1,378 @@ +package nt8 + +import ( + "bufio" + "encoding/hex" + "errors" + "fmt" + "github.com/tarm/serial" + "io" + "log" + "strings" + "time" +) + +type Connection struct { + *serial.Port + + LightCmdCh chan<- []CmdLight +} + +func Open(name string) (*Connection, error) { + p, err := serial.OpenPort(&serial.Config{ + Name: name, + Baud: 9600, + }) + if err != nil { + return nil, err + } + + c := &Connection{p, nil} + go c.handleCmds() + + return c, nil +} + +type Event interface { + event() +} + +type EventButton struct { + Type EventButtonType + Direction Direction + + Value uint8 +} + +type Direction uint8 + +const ( + Down Direction = iota + Up +) + +func (*EventButton) event() {} + +type EventButtonType uint8 + +const ( + ButtonProgram EventButtonType = 0b0010 + ButtonPreview = 0b0001 + ButtonAutoTakeDSK = 0b0011 +) + +type EventSlider struct { + Type EventSliderType + + Value uint8 +} + +func (*EventSlider) event() {} + +type EventSliderType uint8 + +const ( + SliderTbar EventSliderType = iota + RotA + RotB + RotC +) + +type CmdLight struct { + Type EventButtonType + Value uint8 + + State Light +} + +type Light uint8 + +const ( + LightOn Light = iota + LightOff +) + +// first nibble of first byte is ignored +func (c *Connection) WriteCmd(cmd [2]byte) (err error) { + h := strings.ToUpper(hex.EncodeToString(cmd[:])) + + log.Printf("sending: ~%s\r", h[1:]) + _, err = fmt.Fprintf(c.Port, "~%s\r", h[1:]) + return +} + +func (c *Connection) handleCmds() { + ch := make(chan []CmdLight) + c.LightCmdCh = ch + + ledmap := make(map[EventButtonType]uint8) + ledmap[ButtonProgram] = 0xFF + ledmap[ButtonPreview] = 0xFF + ledmap[ButtonAutoTakeDSK] = 0xFF + + for { + changed := make(map[EventButtonType]bool) + + cmds := <-ch + //log.Printf("got cmds: %d %#v", len(cmds), nil) + + for _, cmd := range cmds { + var ( + oldstate = T(ledmap[cmd.Type]&(1<