arson on a massive scale

This commit is contained in:
Riley L. 2024-03-30 18:31:00 +01:00
commit 5233111f25
6 changed files with 812 additions and 0 deletions

35
cmd/log/log.go Normal file
View File

@ -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 <serial>")
}
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)
}
}
}

View File

@ -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"

343
cmd/obsconnect/obs.go Normal file
View File

@ -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))]
}

19
go.mod Normal file
View File

@ -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
)

28
go.sum Normal file
View File

@ -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=

378
mixer.go Normal file
View File

@ -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<<cmd.Value) == 0, LightOn, LightOff)
state = cmd.State
)
// log.Printf("oldstate: %01b state %01b", oldstate, state)
if oldstate != state {
ledmap[cmd.Type] ^= (1 << cmd.Value)
changed[cmd.Type] = true
// send updated state:
}
}
for t := range changed {
err := c.WriteCmd([2]byte{
byte(t),
ledmap[t],
})
if err != nil {
log.Printf("Failed to wrtie CMD: %s", err)
}
}
}
}
// does fancy blinky blinky sequence
func (conn *Connection) InitBlynk() {
cmd := CmdLight{}
c := time.NewTicker(time.Second / 16)
for i := uint8(0); i < 8; i++ {
<-c.C
cmd.Type = ButtonProgram
cmd.Value = 7 - i
cmd.State = LightOn
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 8; i++ {
<-c.C
cmd.Type = ButtonPreview
cmd.Value = 7 - i
cmd.State = LightOn
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 4; i++ {
<-c.C
cmd.Type = ButtonAutoTakeDSK
cmd.Value = 7 - i
cmd.State = LightOn
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 3; i++ {
<-c.C
cmd.Type = ButtonAutoTakeDSK
cmd.Value = i
cmd.State = LightOn
conn.LightCmdCh <- []CmdLight{cmd}
}
// off
for i := uint8(0); i < 8; i++ {
<-c.C
cmd.Type = ButtonProgram
cmd.Value = 7 - i
cmd.State = LightOff
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 8; i++ {
<-c.C
cmd.Type = ButtonPreview
cmd.Value = 7 - i
cmd.State = LightOff
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 4; i++ {
<-c.C
cmd.Type = ButtonAutoTakeDSK
cmd.Value = 7 - i
cmd.State = LightOff
conn.LightCmdCh <- []CmdLight{cmd}
}
for i := uint8(0); i < 3; i++ {
<-c.C
cmd.Type = ButtonAutoTakeDSK
cmd.Value = i
cmd.State = LightOff
conn.LightCmdCh <- []CmdLight{cmd}
}
c.Stop()
}
func (c *Connection) ReadCh() <-chan Event {
ch := make(chan Event, 2)
go func() {
r := bufio.NewReader(c.Port)
cmd := make([]byte, 2)
var (
program byte = 0xFF
preview byte = 0xFF
atdst byte = 0xFF
)
for {
hcmd, err := r.ReadBytes('\r')
if err != nil {
log.Printf("Error reading: %s", err)
}
if errors.Is(err, io.EOF) {
log.Printf("FATAL; EOF")
return
}
if len(hcmd) != 5 {
log.Printf("illigal command received: %02X %s %d", hcmd, hcmd, len(hcmd))
continue
}
//log.Printf("%v %s", hcmd[1:4], hcmd[1:4])
_, err = hex.Decode(cmd, append([]byte("0"), hcmd[1:4]...))
if err != nil {
log.Printf("Failed to decode hex command: %s", err)
continue
}
//log.Printf("%08b %d", cmd, len(cmd))
segment := cmd[0]
//log.Printf("Segment: 0b %04b 0x %1X", segment, segment)
switch segment {
case 0b0010: // program
for i := uint8(0); i < 8; i++ {
mask := byte(1 << i)
state := cmd[1] & mask
oldstate := program & mask
if state != oldstate {
/* log.Printf("mask: %08b", mask)
log.Printf("sta: %08b", state)
log.Printf("osta: %08b", oldstate)
*/
program ^= mask // toggle oldstate
ch <- &EventButton{
Type: ButtonProgram,
Direction: T(state == 0, Down, Up),
Value: i,
}
}
}
case 0b0001: // preview
for i := uint8(0); i < 8; i++ {
mask := byte(1 << i)
state := cmd[1] & mask
oldstate := preview & mask
if state != oldstate {
/* log.Printf("mask: %08b", mask)
log.Printf("sta: %08b", state)
log.Printf("osta: %08b", oldstate)
*/
preview ^= mask // toggle oldstate
ch <- &EventButton{
Type: ButtonPreview,
Direction: T(state == 0, Down, Up),
Value: i,
}
}
}
case 0b0011: // atdsk
for i := uint8(0); i < 8; i++ {
mask := byte(1 << i)
state := cmd[1] & mask
oldstate := atdst & mask
if state != oldstate {
/* log.Printf("mask: %08b", mask)
log.Printf("sta: %08b", state)
log.Printf("osta: %08b", oldstate)
*/
atdst ^= mask // toggle oldstate
ch <- &EventButton{
Type: ButtonAutoTakeDSK,
Direction: T(state == 0, Down, Up),
Value: i,
}
}
}
case 0b0100: // slider1
ch <- &EventSlider{
Type: SliderTbar,
Value: cmd[1],
}
case 0b0101: // slider1
ch <- &EventSlider{
Type: RotA,
Value: cmd[1],
}
case 0b0110: // slider1
ch <- &EventSlider{
Type: RotB,
Value: cmd[1],
}
case 0b0111: // slider1
ch <- &EventSlider{
Type: RotC,
Value: cmd[1],
}
}
}
}()
return ch
}
func T[K any](c bool, a, b K) K {
if c {
return a
}
return b
}