Compare commits

...

49 Commits

Author SHA1 Message Date
Joel Wetzell
168265f0a9 move docker building to main release workflow 2025-12-24 14:05:40 -06:00
Joel Wetzell
caee7b269c fix docker job name 2025-12-24 13:26:28 -06:00
Joel Wetzell
b0cc47236f add debug flag and working directory to launch.json 2025-12-24 13:26:09 -06:00
Joel Wetzell
f6c2b1d9ac Merge pull request #24 from jwetzell/feat/docker
add docker image
2025-12-24 13:24:29 -06:00
Joel Wetzell
f977b845be add github action to build docker image 2025-12-24 13:22:52 -06:00
Joel Wetzell
0166383978 set workdir 2025-12-24 13:22:45 -06:00
Joel Wetzell
66cfaa3091 update Go version in dockerfile 2025-12-24 13:22:34 -06:00
Joel Wetzell
0904d2fcb8 add more to dockerignore 2025-12-24 13:21:53 -06:00
Joel Wetzell
6a13c38e77 add dockerfile 2025-12-24 12:44:25 -06:00
Joel Wetzell
1a8ccfc64a align variable name 2025-12-24 10:54:30 -06:00
Joel Wetzell
2e3bb408c3 Merge pull request #23 from jwetzell/feat/configurable-number-parsing
make base and bitsize configurable for number parsers
2025-12-24 10:35:06 -06:00
Joel Wetzell
3d75165a61 cleanout TODO 2025-12-24 10:34:20 -06:00
Joel Wetzell
50f755f914 make base and bitsize configurable for number parsers 2025-12-24 10:34:06 -06:00
Joel Wetzell
b10e296d0a Merge pull request #22 from jwetzell/feat/udp-server-buffersize
allow configuring buffer size for udp server
2025-12-24 10:27:14 -06:00
Joel Wetzell
91c44420cb allow configuring buffer size for udp server 2025-12-24 10:24:24 -06:00
Joel Wetzell
c1b2fa714e update midi module section 2025-12-23 15:06:23 -06:00
Joel Wetzell
ff1949ce69 use errors.New when not formatting 2025-12-23 15:00:30 -06:00
Joel Wetzell
76583b1fb8 Merge pull request #20 from jwetzell/dependabot/go_modules/github.com/expr-lang/expr-1.17.7
Bump github.com/expr-lang/expr from 1.17.6 to 1.17.7
2025-12-23 14:29:05 -06:00
dependabot[bot]
e6433878b5 Bump github.com/expr-lang/expr from 1.17.6 to 1.17.7
Bumps [github.com/expr-lang/expr](https://github.com/expr-lang/expr) from 1.17.6 to 1.17.7.
- [Release notes](https://github.com/expr-lang/expr/releases)
- [Commits](https://github.com/expr-lang/expr/compare/v1.17.6...v1.17.7)

---
updated-dependencies:
- dependency-name: github.com/expr-lang/expr
  dependency-version: 1.17.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-23 20:27:34 +00:00
Joel Wetzell
3ad067cbb8 Merge pull request #19 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.0.1
Bump github.com/emiago/sipgo from 1.0.1-alpha.0.20251212165843-9c9bcdf9126f to 1.0.1
2025-12-23 14:26:28 -06:00
Joel Wetzell
30b406d601 Merge pull request #21 from jwetzell/dependabot/go_modules/github.com/nats-io/nats.go-1.48.0
Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0
2025-12-23 14:25:25 -06:00
dependabot[bot]
7315d40015 Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 17:33:17 +00:00
dependabot[bot]
e7105b8b39 Bump github.com/emiago/sipgo
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.0.1-alpha.0.20251212165843-9c9bcdf9126f to 1.0.1.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/commits/v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/emiago/sipgo
  dependency-version: 1.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 17:33:08 +00:00
Joel Wetzell
ec40194ecb add TODO 2025-12-21 15:13:06 -06:00
Joel Wetzell
85d8dc5787 cleanup error messages 2025-12-20 14:18:56 -06:00
Joel Wetzell
b27bfb1a6d don't wrap string.filter pattern 2025-12-20 10:59:33 -06:00
Joel Wetzell
fb9c0bc657 cleanup error messages 2025-12-20 07:50:22 -06:00
Joel Wetzell
d32d23041b add usage to cmd 2025-12-19 22:30:18 -06:00
Joel Wetzell
b4149df00a setup slog inside cmd with debug and json flag 2025-12-19 22:27:05 -06:00
Joel Wetzell
61bd4b64f5 setup loggers inside modules with attributes 2025-12-19 22:26:25 -06:00
Joel Wetzell
38d73881c9 return error don't just log it 2025-12-19 22:25:26 -06:00
Joel Wetzell
3138bdfcdb remove TODO's 2025-12-19 12:32:13 -06:00
Joel Wetzell
ba862300b2 fix config example 2025-12-19 09:02:05 -06:00
Joel Wetzell
e414bf336e Merge pull request #18 from jwetzell/test/script-processors
add basic tests for script processors
2025-12-18 20:06:21 -06:00
Joel Wetzell
da95fb5c75 add basic test for script.js 2025-12-18 20:04:52 -06:00
Joel Wetzell
1fdc30aa9e add basic test for script.expr 2025-12-18 20:04:41 -06:00
Joel Wetzell
128920dff9 fix typo in string.split test 2025-12-18 20:03:50 -06:00
Joel Wetzell
9cb7abcc8f add commit to version 2025-12-16 20:45:05 -06:00
Joel Wetzell
991111deba add version flag to cli command 2025-12-16 20:38:26 -06:00
Joel Wetzell
9b1ca9da96 Merge pull request #17 from jwetzell/feat/midi-message-create
add midi.message.create processor
2025-12-16 20:09:57 -06:00
Joel Wetzell
b726745aa2 add alternate casings for type 2025-12-16 20:08:32 -06:00
Joel Wetzell
a8bcf7a785 add ControlChange and ProgramChange 2025-12-16 20:02:16 -06:00
Joel Wetzell
32d8633402 add midi processor to create messages 2025-12-16 19:54:21 -06:00
Joel Wetzell
f3aaa86a1c Merge pull request #16 from jwetzell/feat/split-midi
split midi.client into input/output
2025-12-16 19:43:20 -06:00
Joel Wetzell
25b06ffc6d split midi.client into input/output 2025-12-16 19:24:20 -06:00
Joel Wetzell
ca3d662df7 Merge pull request #15 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.18
Bump gitlab.com/gomidi/midi/v2 from 2.3.16 to 2.3.18
2025-12-15 13:53:09 -06:00
dependabot[bot]
86fe006af8 Bump gitlab.com/gomidi/midi/v2 from 2.3.16 to 2.3.18
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.16 to 2.3.18.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.16...v2.3.18)

---
updated-dependencies:
- dependency-name: gitlab.com/gomidi/midi/v2
  dependency-version: 2.3.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 18:11:35 +00:00
Joel Wetzell
d94232871e add launch.json 2025-12-14 12:14:03 -06:00
Joel Wetzell
5c8605104d update README 2025-12-13 13:59:54 -06:00
64 changed files with 1232 additions and 487 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
Dockerfile
.github
.vscode
README.md
LICENSE

View File

@@ -3,7 +3,7 @@ name: showbridge release
on: on:
push: push:
tags: tags:
- "*" - "v*"
permissions: permissions:
contents: write contents: write
@@ -29,3 +29,40 @@ jobs:
args: release --clean args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
runs-on: ubuntu-latest
needs: goreleaser
steps:
- name: Check out repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup Docker metadata
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: |
jwetzell/showbridge
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
push: true
context: ./
file: ./Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64,linux/arm/v7

18
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch showbridge",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "cmd/showbridge",
"args": ["--debug"],
"cwd": "./"
}
]
}

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
ARG GO_VERSION=1.25.5
FROM golang:${GO_VERSION}-alpine AS build
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build ./cmd/showbridge
FROM scratch
WORKDIR /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build /build/showbridge /app/showbridge
ENTRYPOINT [ "/app/showbridge" ]

View File

@@ -22,8 +22,13 @@ Simple protocol router _/s_
- client - client
- [PosiStageNet](https://posistage.net/) - [PosiStageNet](https://posistage.net/)
- client - client
- MIDI - MIDI (not included in pre-built binaries yet)
- client (not included in pre-built binaries yet) - input
- output
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
- call server
- [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) server

View File

@@ -14,6 +14,8 @@ builds:
goarch: goarch:
- "amd64" - "amd64"
- "arm64" - "arm64"
ldflags:
- '-s -w -X main.version={{.RawVersion}}-{{.ShortCommit}}'
archives: archives:
- formats: [tar.gz] - formats: [tar.gz]

View File

@@ -2,7 +2,7 @@ package main
import ( import (
"context" "context"
"fmt" "errors"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
@@ -13,32 +13,73 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
var (
version = "dev"
)
func main() { func main() {
cmd := &cli.Command{ cmd := &cli.Command{
Name: "showbridge", Name: "showbridge",
Usage: "Simple protocol router /s",
Version: version,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "config", Name: "config",
Value: "./config.yaml", Value: "./config.yaml",
Usage: "path to config file",
},
&cli.BoolFlag{
Name: "debug",
Value: false,
Usage: "set log level to DEBUG",
},
&cli.BoolFlag{
Name: "json",
Value: false,
Usage: "log using JSON",
}, },
}, },
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {
configPath := c.String("config") configPath := c.String("config")
if configPath == "" { if configPath == "" {
return fmt.Errorf("config value cannot be empty") return errors.New("config value cannot be empty")
} }
config, err := readConfig(configPath) config, err := readConfig(configPath)
if err != nil { if err != nil {
return err return err
} }
logLevel := slog.LevelInfo
if c.Bool("debug") {
logLevel = slog.LevelDebug
}
logHandlerOptions := &slog.HandlerOptions{
Level: logLevel,
}
logOutput := os.Stderr
var logHandler slog.Handler = slog.NewTextHandler(logOutput, logHandlerOptions)
if c.Bool("json") {
logHandler = slog.NewJSONHandler(logOutput, logHandlerOptions)
}
logger := slog.New(logHandler)
slog.SetDefault(logger)
router, moduleErrors, routeErrors := showbridge.NewRouter(ctx, config) router, moduleErrors, routeErrors := showbridge.NewRouter(ctx, config)
for _, moduleError := range moduleErrors { for _, moduleError := range moduleErrors {
slog.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error) logger.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error)
} }
for _, routeError := range routeErrors { for _, routeError := range routeErrors {
slog.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error) logger.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error)
} }
router.Run() router.Run()
return nil return nil

View File

@@ -1,6 +1,6 @@
modules: modules:
- id: http - id: http
type: net.http.server type: http.server
params: params:
port: 3000 port: 3000
- id: udp - id: udp

8
go.mod
View File

@@ -5,14 +5,14 @@ go 1.25.3
require ( require (
github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f github.com/emiago/sipgo v1.0.1
github.com/expr-lang/expr v1.17.6 github.com/expr-lang/expr v1.17.7
github.com/jwetzell/free-d-go v0.1.0 github.com/jwetzell/free-d-go v0.1.0
github.com/jwetzell/osc-go v0.1.0 github.com/jwetzell/osc-go v0.1.0
github.com/jwetzell/psn-go v0.3.0 github.com/jwetzell/psn-go v0.3.0
github.com/nats-io/nats.go v1.47.0 github.com/nats-io/nats.go v1.48.0
github.com/urfave/cli/v3 v3.6.1 github.com/urfave/cli/v3 v3.6.1
gitlab.com/gomidi/midi/v2 v2.3.16 gitlab.com/gomidi/midi/v2 v2.3.18
go.bug.st/serial v1.6.4 go.bug.st/serial v1.6.4
modernc.org/quickjs v0.17.0 modernc.org/quickjs v0.17.0
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0

26
go.sum
View File

@@ -8,10 +8,10 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 h1:jqhOZbH40pf2jiUhGaBNk334wOtNYvAaXg/mHOXhy/Y= github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 h1:jqhOZbH40pf2jiUhGaBNk334wOtNYvAaXg/mHOXhy/Y=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111/go.mod h1:3vLCCq8/G/Ei5I64IHtrmBTag+nPLcgXcKeN1KkLtuc= github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111/go.mod h1:3vLCCq8/G/Ei5I64IHtrmBTag+nPLcgXcKeN1KkLtuc=
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f h1:n3wpx7ZyJBr0popXyATnTUZp/AdLIHjKbnKzr4ruF7g= github.com/emiago/sipgo v1.0.1 h1:8eCZ6L/VX3isyByyv1RrBoQ5GyBoRXBHkNMYjwacRfk=
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY= github.com/emiago/sipgo v1.0.1/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -44,34 +44,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM= github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.18 h1:yEAb4+4a8nkPCecWzQB6V/uEU18X1lQCGAQCjP+pyvU=
github.com/pion/rtp v1.8.18/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc= github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY= github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8= github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -86,8 +76,8 @@ github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c= github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo= github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
gitlab.com/gomidi/midi/v2 v2.3.16 h1:yufWSENyjnJ4LFQa9BerzUm4E4aLfTyzw5nmnCteO0c= gitlab.com/gomidi/midi/v2 v2.3.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI=
gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw= gitlab.com/gomidi/midi/v2 v2.3.18/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=

View File

@@ -2,7 +2,7 @@ package module
import ( import (
"context" "context"
"fmt" "errors"
"log/slog" "log/slog"
"net/http" "net/http"
"time" "time"
@@ -16,6 +16,7 @@ type HTTPClient struct {
ctx context.Context ctx context.Context
client *http.Client client *http.Client
router route.RouteIO router route.RouteIO
logger *slog.Logger
} }
func init() { func init() {
@@ -23,7 +24,7 @@ func init() {
Type: "http.client", Type: "http.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) { New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
return &HTTPClient{config: config, ctx: ctx, router: router}, nil return &HTTPClient{config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -43,7 +44,7 @@ func (hc *HTTPClient) Run() error {
} }
<-hc.ctx.Done() <-hc.ctx.Done()
slog.Debug("router context done in module", "id", hc.Id()) hc.logger.Debug("router context done in module")
return nil return nil
} }
@@ -52,11 +53,11 @@ func (hc *HTTPClient) Output(payload any) error {
payloadRequest, ok := payload.(*http.Request) payloadRequest, ok := payload.(*http.Request)
if !ok { if !ok {
return fmt.Errorf("http.client is only able to output an http.Request") return errors.New("http.client is only able to output an http.Request")
} }
if hc.client == nil { if hc.client == nil {
return fmt.Errorf("http.client client is nil") return errors.New("http.client client is nil")
} }
response, err := hc.client.Do(payloadRequest) response, err := hc.client.Do(payloadRequest)

View File

@@ -3,6 +3,7 @@ package module
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
@@ -16,6 +17,7 @@ type HTTPServer struct {
Port uint16 Port uint16
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
logger *slog.Logger
} }
type ResponseData struct { type ResponseData struct {
@@ -30,16 +32,16 @@ func init() {
params := config.Params params := config.Params
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("http.server requires a port parameter") return nil, errors.New("http.server requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("http.server port must be uint16") return nil, errors.New("http.server port must be uint16")
} }
return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router}, nil return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -89,12 +91,11 @@ func (hs *HTTPServer) Run() error {
go func() { go func() {
<-hs.ctx.Done() <-hs.ctx.Done()
slog.Debug("router context done in module", "id", hs.Id()) hs.logger.Debug("router context done in module")
httpServer.Close() httpServer.Close()
}() }()
err := httpServer.ListenAndServe() err := httpServer.ListenAndServe()
slog.Debug("http.server closed", "id", hs.Id())
// TODO(jwetzell): handle server closed error differently // TODO(jwetzell): handle server closed error differently
if err != nil { if err != nil {
return err return err
@@ -105,5 +106,5 @@ func (hs *HTTPServer) Run() error {
} }
func (hs *HTTPServer) Output(payload any) error { func (hs *HTTPServer) Output(payload any) error {
return fmt.Errorf("http.server output is not implemented") return errors.New("http.server output is not implemented")
} }

View File

@@ -2,7 +2,7 @@ package module
import ( import (
"context" "context"
"fmt" "errors"
"log/slog" "log/slog"
"time" "time"
@@ -16,6 +16,7 @@ type Interval struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
ticker *time.Ticker ticker *time.Ticker
logger *slog.Logger
} }
func init() { func init() {
@@ -26,16 +27,16 @@ func init() {
duration, ok := params["duration"] duration, ok := params["duration"]
if !ok { if !ok {
return nil, fmt.Errorf("gen.interval requires a duration parameter") return nil, errors.New("gen.interval requires a duration parameter")
} }
durationNum, ok := duration.(float64) durationNum, ok := duration.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("gen.interval duration must be number") return nil, errors.New("gen.interval duration must be number")
} }
return &Interval{Duration: uint32(durationNum), config: config, ctx: ctx, router: router}, nil return &Interval{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -56,7 +57,7 @@ func (i *Interval) Run() error {
for { for {
select { select {
case <-i.ctx.Done(): case <-i.ctx.Done():
slog.Debug("router context done in module", "id", i.Id()) i.logger.Debug("router context done in module")
return nil return nil
case <-ticker.C: case <-ticker.C:
if i.router != nil { if i.router != nil {

View File

@@ -1,119 +0,0 @@
//go:build cgo
package module
import (
"context"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIClient struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
InputPort string
OutputPort string
SendFunc func(midi.Message) error
}
func init() {
RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "midi.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
input, ok := params["input"]
if !ok {
return nil, fmt.Errorf("midi.client requires a input parameter")
}
inputString, ok := input.(string)
if !ok {
return nil, fmt.Errorf("midi.client input must be a string")
}
output, ok := params["output"]
if !ok {
return nil, fmt.Errorf("midi.client requires a output parameter")
}
outputString, ok := output.(string)
if !ok {
return nil, fmt.Errorf("midi.client output must be a string")
}
return &MIDIClient{config: config, InputPort: inputString, OutputPort: outputString, ctx: ctx, router: router}, nil
},
})
}
func (mc *MIDIClient) Id() string {
return mc.config.Id
}
func (mc *MIDIClient) Type() string {
return mc.config.Type
}
func (mc *MIDIClient) Run() error {
defer midi.CloseDriver()
in, err := midi.FindInPort(mc.InputPort)
if err != nil {
return fmt.Errorf("midi.client can't find input port: %s", mc.InputPort)
}
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mc.router != nil {
// TODO(jwetzell): unpack MIDI messsage into something more useful?
mc.router.HandleInput(mc.Id(), msg)
}
}, midi.UseSysEx())
if err != nil {
return err
}
defer stop()
out, err := midi.FindOutPort(mc.OutputPort)
if err != nil {
return fmt.Errorf("midi.client can't find output port: %s", mc.OutputPort)
}
send, err := midi.SendTo(out)
if err != nil {
return err
}
mc.SendFunc = send
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
return nil
}
func (mc *MIDIClient) Output(payload any) error {
if mc.SendFunc == nil {
return fmt.Errorf("midi.client output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
if !ok {
return fmt.Errorf("midi.client can only ouptut midi.Message")
}
return mc.SendFunc(payloadMessage)
}

View File

@@ -0,0 +1,83 @@
//go:build cgo
package module
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIInput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() {
RegisterModule(ModuleRegistration{
Type: "midi.input",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, errors.New("midi.input requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, errors.New("midi.input port must be a string")
}
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (mi *MIDIInput) Id() string {
return mi.config.Id
}
func (mi *MIDIInput) Type() string {
return mi.config.Type
}
func (mi *MIDIInput) Run() error {
defer midi.CloseDriver()
in, err := midi.FindInPort(mi.Port)
if err != nil {
return fmt.Errorf("midi.input can't find input port: %s", mi.Port)
}
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mi.router != nil {
mi.router.HandleInput(mi.Id(), msg)
}
}, midi.UseSysEx())
if err != nil {
return err
}
defer stop()
<-mi.ctx.Done()
mi.logger.Debug("router context done in module")
return nil
}
func (mi *MIDIInput) Output(payload any) error {
return errors.New("midi.input output is not implemented")
}

View File

@@ -0,0 +1,90 @@
//go:build cgo
package module
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIOutput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() {
RegisterModule(ModuleRegistration{
Type: "midi.output",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, errors.New("midi.output requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, errors.New("midi.output port must be a string")
}
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (mo *MIDIOutput) Id() string {
return mo.config.Id
}
func (mo *MIDIOutput) Type() string {
return mo.config.Type
}
func (mo *MIDIOutput) Run() error {
defer midi.CloseDriver()
out, err := midi.FindOutPort(mo.Port)
if err != nil {
return fmt.Errorf("midi.output can't find output port: %s", mo.Port)
}
send, err := midi.SendTo(out)
if err != nil {
return err
}
mo.SendFunc = send
<-mo.ctx.Done()
mo.logger.Debug("router context done in module")
return nil
}
func (mo *MIDIOutput) Output(payload any) error {
if mo.SendFunc == nil {
return errors.New("midi.output output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
if !ok {
return errors.New("midi.output can only ouptut midi.Message")
}
return mo.SendFunc(payloadMessage)
}

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
@@ -18,6 +19,7 @@ type MQTTClient struct {
ClientID string ClientID string
Topic string Topic string
client mqtt.Client client mqtt.Client
logger *slog.Logger
} }
func init() { func init() {
@@ -28,40 +30,40 @@ func init() {
broker, ok := params["broker"] broker, ok := params["broker"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client requires a broker parameter") return nil, errors.New("mqtt.client requires a broker parameter")
} }
brokerString, ok := broker.(string) brokerString, ok := broker.(string)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client broker must be string") return nil, errors.New("mqtt.client broker must be string")
} }
topic, ok := params["topic"] topic, ok := params["topic"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client requires a topic parameter") return nil, errors.New("mqtt.client requires a topic parameter")
} }
topicString, ok := topic.(string) topicString, ok := topic.(string)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client topic must be string") return nil, errors.New("mqtt.client topic must be string")
} }
clientId, ok := params["clientId"] clientId, ok := params["clientId"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client requires a clientId parameter") return nil, errors.New("mqtt.client requires a clientId parameter")
} }
clientIdString, ok := clientId.(string) clientIdString, ok := clientId.(string)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.client clientId must be string") return nil, errors.New("mqtt.client clientId must be string")
} }
return &MQTTClient{config: config, Broker: brokerString, Topic: topicString, ClientID: clientIdString, ctx: ctx, router: router}, nil return &MQTTClient{config: config, Broker: brokerString, Topic: topicString, ClientID: clientIdString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -99,7 +101,7 @@ func (mc *MQTTClient) Run() error {
} }
<-mc.ctx.Done() <-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id()) mc.logger.Debug("router context done in module")
return nil return nil
} }
@@ -109,15 +111,15 @@ func (mc *MQTTClient) Output(payload any) error {
fmt.Printf("payload type: %T\n", payload) fmt.Printf("payload type: %T\n", payload)
if !ok { if !ok {
return fmt.Errorf("mqtt.client is only able to output a MQTTMessage") return errors.New("mqtt.client is only able to output a MQTTMessage")
} }
if mc.client == nil { if mc.client == nil {
return fmt.Errorf("mqtt.client client is not setup") return errors.New("mqtt.client client is not setup")
} }
if !mc.client.IsConnected() { if !mc.client.IsConnected() {
return fmt.Errorf("mqtt.client is not connected") return errors.New("mqtt.client is not connected")
} }
token := mc.client.Publish(payloadMessage.Topic(), payloadMessage.Qos(), payloadMessage.Retained(), payloadMessage.Payload()) token := mc.client.Publish(payloadMessage.Topic(), payloadMessage.Qos(), payloadMessage.Retained(), payloadMessage.Payload())

View File

@@ -2,7 +2,7 @@ package module
import ( import (
"context" "context"
"fmt" "errors"
"log/slog" "log/slog"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -18,6 +18,7 @@ type NATSClient struct {
URL string URL string
Subject string Subject string
client *nats.Conn client *nats.Conn
logger *slog.Logger
} }
func init() { func init() {
@@ -28,28 +29,28 @@ func init() {
url, ok := params["url"] url, ok := params["url"]
if !ok { if !ok {
return nil, fmt.Errorf("nats.client requires a url parameter") return nil, errors.New("nats.client requires a url parameter")
} }
urlString, ok := url.(string) urlString, ok := url.(string)
if !ok { if !ok {
return nil, fmt.Errorf("nats.client url must be string") return nil, errors.New("nats.client url must be string")
} }
subject, ok := params["subject"] subject, ok := params["subject"]
if !ok { if !ok {
return nil, fmt.Errorf("nats.client requires a subject parameter") return nil, errors.New("nats.client requires a subject parameter")
} }
subjectString, ok := subject.(string) subjectString, ok := subject.(string)
if !ok { if !ok {
return nil, fmt.Errorf("nats.client subject must be string") return nil, errors.New("nats.client subject must be string")
} }
return &NATSClient{config: config, URL: urlString, Subject: subjectString, ctx: ctx, router: router}, nil return &NATSClient{config: config, URL: urlString, Subject: subjectString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -87,7 +88,7 @@ func (nc *NATSClient) Run() error {
defer sub.Unsubscribe() defer sub.Unsubscribe()
<-nc.ctx.Done() <-nc.ctx.Done()
slog.Debug("router context done in module", "id", nc.Id()) nc.logger.Debug("router context done in module")
return nil return nil
} }
@@ -96,15 +97,15 @@ func (nc *NATSClient) Output(payload any) error {
payloadMessage, ok := payload.(processor.NATSMessage) payloadMessage, ok := payload.(processor.NATSMessage)
if !ok { if !ok {
return fmt.Errorf("nats.client is only able to output NATSMessage") return errors.New("nats.client is only able to output NATSMessage")
} }
if nc.client == nil { if nc.client == nil {
return fmt.Errorf("nats.client client is not setup") return errors.New("nats.client client is not setup")
} }
if !nc.client.IsConnected() { if !nc.client.IsConnected() {
return fmt.Errorf("nats.client is not connected") return errors.New("nats.client is not connected")
} }
err := nc.client.Publish(payloadMessage.Subject, payloadMessage.Payload) err := nc.client.Publish(payloadMessage.Subject, payloadMessage.Payload)

View File

@@ -18,6 +18,7 @@ type PSNClient struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
decoder *psn.Decoder decoder *psn.Decoder
logger *slog.Logger
} }
func init() { func init() {
@@ -25,7 +26,7 @@ func init() {
Type: "psn.client", Type: "psn.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) { New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
return &PSNClient{config: config, decoder: psn.NewDecoder(), ctx: ctx, router: router}, nil return &PSNClient{config: config, decoder: psn.NewDecoder(), ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -58,7 +59,7 @@ func (pc *PSNClient) Run() error {
select { select {
case <-pc.ctx.Done(): case <-pc.ctx.Done():
// TODO(jwetzell): cleanup? // TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", pc.Id()) pc.logger.Debug("router context done in module")
return nil return nil
default: default:
pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -76,7 +77,7 @@ func (pc *PSNClient) Run() error {
message := buffer[:numBytes] message := buffer[:numBytes]
err := pc.decoder.Decode(message) err := pc.decoder.Decode(message)
if err != nil { if err != nil {
slog.Error("psn.client problem decoding psn traffic", "id", pc.Id(), "error", err) pc.logger.Error("psn.client problem decoding psn traffic", "error", err)
} }
if pc.router != nil { if pc.router != nil {
@@ -84,7 +85,7 @@ func (pc *PSNClient) Run() error {
pc.router.HandleInput(pc.Id(), tracker) pc.router.HandleInput(pc.Id(), tracker)
} }
} else { } else {
slog.Error("psn.client has no router", "id", pc.Id()) pc.logger.Error("psn.client has no router")
} }
} }
} }

View File

@@ -4,6 +4,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"time" "time"
@@ -22,24 +23,24 @@ type SerialClient struct {
Framer framer.Framer Framer framer.Framer
Mode *serial.Mode Mode *serial.Mode
port serial.Port port serial.Port
logger *slog.Logger
} }
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "serial.client", Type: "serial.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) { New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params params := config.Params
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("serial.client requires a port parameter") return nil, errors.New("serial.client requires a port parameter")
} }
portString, ok := port.(string) portString, ok := port.(string)
if !ok { if !ok {
return nil, fmt.Errorf("serial.client port must be a string") return nil, errors.New("serial.client port must be a string")
} }
framingMethod := "RAW" framingMethod := "RAW"
@@ -50,7 +51,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string) framingMethodString, ok := framingMethodRaw.(string)
if !ok { if !ok {
return nil, fmt.Errorf("serial.client framing method must be a string") return nil, errors.New("serial.client framing method must be a string")
} }
framingMethod = framingMethodString framingMethod = framingMethodString
} }
@@ -63,94 +64,94 @@ func init() {
buadRate, ok := params["baudRate"] buadRate, ok := params["baudRate"]
if !ok { if !ok {
return nil, fmt.Errorf("serial.client requires a baudRate parameter") return nil, errors.New("serial.client requires a baudRate parameter")
} }
baudRateNum, ok := buadRate.(float64) baudRateNum, ok := buadRate.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("serial.client baudRate must be a number") return nil, errors.New("serial.client baudRate must be a number")
} }
mode := serial.Mode{ mode := serial.Mode{
BaudRate: int(baudRateNum), BaudRate: int(baudRateNum),
} }
return &SerialClient{config: config, Port: portString, Framer: framer, Mode: &mode, ctx: ctx, router: router}, nil return &SerialClient{config: config, Port: portString, Framer: framer, Mode: &mode, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
func (mc *SerialClient) Id() string { func (sc *SerialClient) Id() string {
return mc.config.Id return sc.config.Id
} }
func (mc *SerialClient) Type() string { func (sc *SerialClient) Type() string {
return mc.config.Type return sc.config.Type
} }
func (mc *SerialClient) SetupPort() error { func (sc *SerialClient) SetupPort() error {
port, err := serial.Open(mc.Port, mc.Mode) port, err := serial.Open(sc.Port, sc.Mode)
if err != nil { if err != nil {
return fmt.Errorf("serial.client can't open input port: %s", mc.Port) return fmt.Errorf("serial.client can't open input port: %s", sc.Port)
} }
mc.port = port sc.port = port
return nil return nil
} }
func (mc *SerialClient) Run() error { func (sc *SerialClient) Run() error {
// TODO(jwetzell): shutdown with router.Context properly // TODO(jwetzell): shutdown with router.Context properly
go func() { go func() {
<-mc.ctx.Done() <-sc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id()) sc.logger.Debug("router context done in module")
if mc.port != nil { if sc.port != nil {
mc.port.Close() sc.port.Close()
} }
}() }()
for { for {
err := mc.SetupPort() err := sc.SetupPort()
if err != nil { if err != nil {
if mc.ctx.Err() != nil { if sc.ctx.Err() != nil {
slog.Debug("router context done in module", "id", mc.Id()) sc.logger.Debug("router context done in module")
return nil return nil
} }
slog.Error("serial.client", "id", mc.Id(), "error", err.Error()) sc.logger.Error("serial.client", "error", err.Error())
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
continue continue
} }
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
select { select {
case <-mc.ctx.Done(): case <-sc.ctx.Done():
slog.Debug("router context done in module", "id", mc.Id()) sc.logger.Debug("router context done in module")
return nil return nil
default: default:
READ: READ:
for { for {
select { select {
case <-mc.ctx.Done(): case <-sc.ctx.Done():
slog.Debug("router context done in module", "id", mc.Id()) sc.logger.Debug("router context done in module")
return nil return nil
default: default:
byteCount, err := mc.port.Read(buffer) byteCount, err := sc.port.Read(buffer)
if err != nil { if err != nil {
mc.Framer.Clear() sc.Framer.Clear()
break READ break READ
} }
if mc.Framer != nil { if sc.Framer != nil {
if byteCount > 0 { if byteCount > 0 {
messages := mc.Framer.Decode(buffer[0:byteCount]) messages := sc.Framer.Decode(buffer[0:byteCount])
for _, message := range messages { for _, message := range messages {
if mc.router != nil { if sc.router != nil {
mc.router.HandleInput(mc.Id(), message) sc.router.HandleInput(sc.Id(), message)
} else { } else {
slog.Error("serial.client has no router", "id", mc.Id()) sc.logger.Error("serial.client has no router")
} }
} }
} }
@@ -161,14 +162,14 @@ func (mc *SerialClient) Run() error {
} }
} }
func (mc *SerialClient) Output(payload any) error { func (sc *SerialClient) Output(payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return fmt.Errorf("serial.client can only ouptut bytes") return errors.New("serial.client can only ouptut bytes")
} }
_, err := mc.port.Write(mc.Framer.Encode(payloadBytes)) _, err := sc.port.Write(sc.Framer.Encode(payloadBytes))
return err return err
} }

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
@@ -24,6 +25,7 @@ type SIPCallServer struct {
Transport string Transport string
UserAgent string UserAgent string
dg *diago.Diago dg *diago.Diago
logger *slog.Logger
} }
type SIPCallMessage struct { type SIPCallMessage struct {
@@ -42,7 +44,7 @@ func init() {
specificPortNum, ok := port.(float64) specificPortNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("sip.call.server port must be a number") return nil, errors.New("sip.call.server port must be a number")
} }
portNum = int(specificPortNum) portNum = int(specificPortNum)
} }
@@ -55,7 +57,7 @@ func init() {
specificIpString, ok := ip.(string) specificIpString, ok := ip.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.call.server ip must be a string") return nil, errors.New("sip.call.server ip must be a string")
} }
ipString = specificIpString ipString = specificIpString
} }
@@ -68,7 +70,7 @@ func init() {
specificTransportString, ok := transport.(string) specificTransportString, ok := transport.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.call.server transport must be a string") return nil, errors.New("sip.call.server transport must be a string")
} }
transportString = specificTransportString transportString = specificTransportString
} }
@@ -81,28 +83,28 @@ func init() {
specificTransportString, ok := userAgent.(string) specificTransportString, ok := userAgent.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.call.server userAgent must be a string") return nil, errors.New("sip.call.server userAgent must be a string")
} }
userAgentString = specificTransportString userAgentString = specificTransportString
} }
return &SIPCallServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString}, nil return &SIPCallServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
func (sds *SIPCallServer) Id() string { func (scs *SIPCallServer) Id() string {
return sds.config.Id return scs.config.Id
} }
func (sds *SIPCallServer) Type() string { func (scs *SIPCallServer) Type() string {
return sds.config.Type return scs.config.Type
} }
func (sds *SIPCallServer) Run() error { func (scs *SIPCallServer) Run() error {
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil)) diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA( ua, _ := sipgo.NewUA(
sipgo.WithUserAgent(sds.UserAgent), sipgo.WithUserAgent(scs.UserAgent),
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)), sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)), sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
) )
@@ -112,74 +114,74 @@ func (sds *SIPCallServer) Run() error {
media.SetDefaultLogger(diagoLogger) media.SetDefaultLogger(diagoLogger)
dg := diago.NewDiago(ua, diago.WithLogger(diagoLogger), diago.WithTransport( dg := diago.NewDiago(ua, diago.WithLogger(diagoLogger), diago.WithTransport(
diago.Transport{ diago.Transport{
Transport: sds.Transport, Transport: scs.Transport,
BindHost: sds.IP, BindHost: scs.IP,
BindPort: sds.Port, BindPort: scs.Port,
}, },
)) ))
go func() { go func() {
dg.Serve(sds.ctx, func(inDialog *diago.DialogServerSession) { dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
sds.HandleCall(inDialog) scs.HandleCall(inDialog)
}) })
}() }()
sds.dg = dg scs.dg = dg
<-sds.ctx.Done() <-scs.ctx.Done()
slog.Debug("router context done in module", "id", sds.Id()) scs.logger.Debug("router context done in module")
return nil return nil
} }
func (sds *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) { func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
inDialog.Trying() inDialog.Trying()
inDialog.Ringing() inDialog.Ringing()
inDialog.Answer() inDialog.Answer()
sds.router.HandleInput(sds.Id(), SIPCallMessage{ scs.router.HandleInput(scs.Id(), SIPCallMessage{
To: inDialog.ToUser(), To: inDialog.ToUser(),
}) })
<-inDialog.Context().Done() <-inDialog.Context().Done()
} }
func (sds *SIPCallServer) Output(payload any) error { func (scs *SIPCallServer) Output(payload any) error {
payloadMsg, ok := payload.(string) payloadMsg, ok := payload.(string)
if !ok { if !ok {
return fmt.Errorf("sip.call.server output payload must be of type string") return errors.New("sip.call.server output payload must be of type string")
} }
if sds.dg == nil { if scs.dg == nil {
return fmt.Errorf("sip.call.server diago is not initialized") return errors.New("sip.call.server diago is not initialized")
} }
var uri sip.Uri var uri sip.Uri
err := sip.ParseUri(payloadMsg, &uri) err := sip.ParseUri(payloadMsg, &uri)
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %v", err) return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %s", err)
} }
outDialog, err := sds.dg.NewDialog(uri, diago.NewDialogOptions{ outDialog, err := scs.dg.NewDialog(uri, diago.NewDialogOptions{
Transport: sds.Transport, Transport: scs.Transport,
}) })
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to create new dialog: %v", err) return fmt.Errorf("sip.call.server failed to create new dialog: %s", err)
} }
err = outDialog.Invite(sds.ctx, diago.InviteClientOptions{}) err = outDialog.Invite(scs.ctx, diago.InviteClientOptions{})
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to send invite: %v", err) return fmt.Errorf("sip.call.server failed to send invite: %s", err)
} }
err = outDialog.Ack(sds.ctx) err = outDialog.Ack(scs.ctx)
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to send ack: %v", err) return fmt.Errorf("sip.call.server failed to send ack: %s", err)
} }
// TODO(jwetzell): make this configurable // TODO(jwetzell): make this configurable
// NOTE(jwetzell): wait 5 seconds before hanging up the call // NOTE(jwetzell): wait 5 seconds before hanging up the call
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
err = outDialog.Hangup(sds.ctx) err = outDialog.Hangup(scs.ctx)
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to hangup call: %v", err) return fmt.Errorf("sip.call.server failed to hangup call: %s", err)
} }
return nil return nil
} }

View File

@@ -2,7 +2,7 @@ package module
import ( import (
"context" "context"
"fmt" "errors"
"io" "io"
"log/slog" "log/slog"
"strings" "strings"
@@ -24,6 +24,7 @@ type SIPDTMFServer struct {
Port int Port int
Transport string Transport string
Separator string Separator string
logger *slog.Logger
} }
type SIPDTMFMessage struct { type SIPDTMFMessage struct {
@@ -43,7 +44,7 @@ func init() {
specificPortNum, ok := port.(float64) specificPortNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("sip.dtmf.server port must be a number") return nil, errors.New("sip.dtmf.server port must be a number")
} }
portNum = int(specificPortNum) portNum = int(specificPortNum)
} }
@@ -56,7 +57,7 @@ func init() {
specificIpString, ok := ip.(string) specificIpString, ok := ip.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.dtmf.server ip must be a string") return nil, errors.New("sip.dtmf.server ip must be a string")
} }
ipString = specificIpString ipString = specificIpString
} }
@@ -69,28 +70,28 @@ func init() {
specificTransportString, ok := transport.(string) specificTransportString, ok := transport.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.dtmf.server transport must be a string") return nil, errors.New("sip.dtmf.server transport must be a string")
} }
transportString = specificTransportString transportString = specificTransportString
} }
separator, ok := params["separator"] separator, ok := params["separator"]
if !ok { if !ok {
return nil, fmt.Errorf("sip.dtmf.server requires a separator parameter") return nil, errors.New("sip.dtmf.server requires a separator parameter")
} }
separatorString, ok := separator.(string) separatorString, ok := separator.(string)
if !ok { if !ok {
return nil, fmt.Errorf("sip.dtmf.server separator must be a string") return nil, errors.New("sip.dtmf.server separator must be a string")
} }
if len(separatorString) != 1 { if len(separatorString) != 1 {
return nil, fmt.Errorf("sip.dtmf.server separator must be a single character") return nil, errors.New("sip.dtmf.server separator must be a single character")
} }
if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) { if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) {
return nil, fmt.Errorf("sip.dtmf.server separator must be a valid DTMF character") return nil, errors.New("sip.dtmf.server separator must be a valid DTMF character")
} }
return &SIPDTMFServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, Separator: separatorString}, nil return &SIPDTMFServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, Separator: separatorString, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -131,7 +132,7 @@ func (sds *SIPDTMFServer) Run() error {
} }
<-sds.ctx.Done() <-sds.ctx.Done()
slog.Debug("router context done in module", "id", sds.Id()) sds.logger.Debug("router context done in module")
return nil return nil
} }
@@ -159,5 +160,5 @@ func (sds *SIPDTMFServer) HandleCall(inDialog *diago.DialogServerSession) error
} }
func (sds *SIPDTMFServer) Output(payload any) error { func (sds *SIPDTMFServer) Output(payload any) error {
return fmt.Errorf("sip.dtmf.server output is not implemented") return errors.New("sip.dtmf.server output is not implemented")
} }

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
@@ -19,6 +20,7 @@ type TCPClient struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
Addr *net.TCPAddr Addr *net.TCPAddr
logger *slog.Logger
} }
func init() { func init() {
@@ -29,24 +31,24 @@ func init() {
host, ok := params["host"] host, ok := params["host"]
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.client requires a host parameter") return nil, errors.New("net.tcp.client requires a host parameter")
} }
hostString, ok := host.(string) hostString, ok := host.(string)
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.client host must be string") return nil, errors.New("net.tcp.client host must be string")
} }
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.client requires a port parameter") return nil, errors.New("net.tcp.client requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.client port must be a number") return nil, errors.New("net.tcp.client port must be a number")
} }
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", hostString, uint16(portNum))) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
@@ -62,7 +64,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string) framingMethodString, ok := framingMethodRaw.(string)
if !ok { if !ok {
return nil, fmt.Errorf("misc.serial.client framing method must be a string") return nil, errors.New("misc.serial.client framing method must be a string")
} }
framingMethod = framingMethodString framingMethod = framingMethodString
} }
@@ -73,7 +75,7 @@ func init() {
return nil, err return nil, err
} }
return &TCPClient{framer: framer, Addr: addr, config: config, ctx: ctx, router: router}, nil return &TCPClient{framer: framer, Addr: addr, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -91,7 +93,7 @@ func (tc *TCPClient) Run() error {
// TODO(jwetzell): shutdown with router.Context properly // TODO(jwetzell): shutdown with router.Context properly
go func() { go func() {
<-tc.ctx.Done() <-tc.ctx.Done()
slog.Debug("router context done in module", "id", tc.Id()) tc.logger.Debug("router context done in module")
if tc.conn != nil { if tc.conn != nil {
tc.conn.Close() tc.conn.Close()
} }
@@ -101,10 +103,10 @@ func (tc *TCPClient) Run() error {
err := tc.SetupConn() err := tc.SetupConn()
if err != nil { if err != nil {
if tc.ctx.Err() != nil { if tc.ctx.Err() != nil {
slog.Debug("router context done in module", "id", tc.Id()) tc.logger.Debug("router context done in module")
return nil return nil
} }
slog.Error("net.tcp.client", "id", tc.Id(), "error", err.Error()) tc.logger.Error("net.tcp.client", "error", err.Error())
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
continue continue
} }
@@ -112,14 +114,14 @@ func (tc *TCPClient) Run() error {
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
select { select {
case <-tc.ctx.Done(): case <-tc.ctx.Done():
slog.Debug("router context done in module", "id", tc.Id()) tc.logger.Debug("router context done in module")
return nil return nil
default: default:
READ: READ:
for { for {
select { select {
case <-tc.ctx.Done(): case <-tc.ctx.Done():
slog.Debug("router context done in module", "id", tc.Id()) tc.logger.Debug("router context done in module")
return nil return nil
default: default:
byteCount, err := tc.conn.Read(buffer) byteCount, err := tc.conn.Read(buffer)
@@ -136,7 +138,7 @@ func (tc *TCPClient) Run() error {
if tc.router != nil { if tc.router != nil {
tc.router.HandleInput(tc.Id(), message) tc.router.HandleInput(tc.Id(), message)
} else { } else {
slog.Error("net.tcp.client has no router", "id", tc.Id()) tc.logger.Error("net.tcp.client has no router")
} }
} }
} }
@@ -163,7 +165,7 @@ func (tc *TCPClient) Output(payload any) error {
} }
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return fmt.Errorf("net.tcp.client is only able to output bytes") return errors.New("net.tcp.client is only able to output bytes")
} }
_, err := tc.conn.Write(tc.framer.Encode(payloadBytes)) _, err := tc.conn.Write(tc.framer.Encode(payloadBytes))
return err return err

View File

@@ -26,6 +26,7 @@ type TCPServer struct {
wg sync.WaitGroup wg sync.WaitGroup
connections []*net.TCPConn connections []*net.TCPConn
connectionsMu sync.RWMutex connectionsMu sync.RWMutex
logger *slog.Logger
} }
func init() { func init() {
@@ -35,13 +36,13 @@ func init() {
params := config.Params params := config.Params
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.server requires a port parameter") return nil, errors.New("net.tcp.server requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.server port must be a number") return nil, errors.New("net.tcp.server port must be a number")
} }
framingMethod := "RAW" framingMethod := "RAW"
@@ -52,7 +53,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string) framingMethodString, ok := framingMethodRaw.(string)
if !ok { if !ok {
return nil, fmt.Errorf("misc.serial.client framing method must be a string") return nil, errors.New("misc.serial.client framing method must be a string")
} }
framingMethod = framingMethodString framingMethod = framingMethodString
} }
@@ -71,7 +72,7 @@ func init() {
specificIpString, ok := ip.(string) specificIpString, ok := ip.(string)
if !ok { if !ok {
return nil, fmt.Errorf("net.tcp.server ip must be a string") return nil, errors.New("net.tcp.server ip must be a string")
} }
ipString = specificIpString ipString = specificIpString
} }
@@ -81,7 +82,7 @@ func init() {
return nil, err return nil, err
} }
return &TCPServer{Framer: framer, Addr: addr, config: config, quit: make(chan interface{}), ctx: ctx, router: router}, nil return &TCPServer{Framer: framer, Addr: addr, config: config, quit: make(chan interface{}), ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -98,7 +99,7 @@ func (ts *TCPServer) handleClient(client *net.TCPConn) {
ts.connectionsMu.Lock() ts.connectionsMu.Lock()
ts.connections = append(ts.connections, client) ts.connections = append(ts.connections, client)
ts.connectionsMu.Unlock() ts.connectionsMu.Unlock()
slog.Debug("net.tcp.server connection accepted", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String()) ts.logger.Debug("net.tcp.server connection accepted", "remoteAddr", client.RemoteAddr().String())
defer client.Close() defer client.Close()
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
@@ -125,7 +126,7 @@ ClientRead:
break break
} }
} }
slog.Debug("net.tcp.server connection reset", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String()) ts.logger.Debug("net.tcp.server connection reset", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock() ts.connectionsMu.Unlock()
} }
} }
@@ -138,7 +139,7 @@ ClientRead:
break break
} }
} }
slog.Debug("net.tcp.server stream ended", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String()) ts.logger.Debug("net.tcp.server stream ended", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock() ts.connectionsMu.Unlock()
} }
return return
@@ -150,7 +151,7 @@ ClientRead:
if ts.router != nil { if ts.router != nil {
ts.router.HandleInput(ts.Id(), message) ts.router.HandleInput(ts.Id(), message)
} else { } else {
slog.Error("net.tcp.server has no router", "id", ts.Id()) ts.logger.Error("net.tcp.server has no router")
} }
} }
} }
@@ -170,7 +171,7 @@ func (ts *TCPServer) Run() error {
<-ts.ctx.Done() <-ts.ctx.Done()
close(ts.quit) close(ts.quit)
listener.Close() listener.Close()
slog.Debug("router context done in module", "id", ts.Id()) ts.logger.Debug("router context done in module")
}() }()
AcceptLoop: AcceptLoop:
@@ -181,7 +182,7 @@ AcceptLoop:
case <-ts.quit: case <-ts.quit:
break AcceptLoop break AcceptLoop
default: default:
slog.Debug("net.tcp.server problem with listener", "error", err) ts.logger.Debug("net.tcp.server problem with listener", "error", err)
} }
} else { } else {
ts.wg.Add(1) ts.wg.Add(1)
@@ -200,7 +201,7 @@ func (ts *TCPServer) Output(payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return fmt.Errorf("net.tcp.server is only able to output bytes") return errors.New("net.tcp.server is only able to output bytes")
} }
ts.connectionsMu.Lock() ts.connectionsMu.Lock()
errorString := "" errorString := ""
@@ -216,5 +217,5 @@ func (ts *TCPServer) Output(payload any) error {
if errorString == "" { if errorString == "" {
return nil return nil
} }
return fmt.Errorf("%s", errorString) return fmt.Errorf("net.tcp.server error during output: %s", errorString)
} }

View File

@@ -2,7 +2,7 @@ package module
import ( import (
"context" "context"
"fmt" "errors"
"log/slog" "log/slog"
"time" "time"
@@ -16,6 +16,7 @@ type Timer struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
timer *time.Timer timer *time.Timer
logger *slog.Logger
} }
func init() { func init() {
@@ -26,16 +27,16 @@ func init() {
duration, ok := params["duration"] duration, ok := params["duration"]
if !ok { if !ok {
return nil, fmt.Errorf("gen.timer requires a duration parameter") return nil, errors.New("gen.timer requires a duration parameter")
} }
durationNum, ok := duration.(float64) durationNum, ok := duration.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("gen.timer duration must be a number") return nil, errors.New("gen.timer duration must be a number")
} }
return &Timer{Duration: uint32(durationNum), config: config, ctx: ctx, router: router}, nil return &Timer{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -55,7 +56,7 @@ func (t *Timer) Run() error {
select { select {
case <-t.ctx.Done(): case <-t.ctx.Done():
t.timer.Stop() t.timer.Stop()
slog.Debug("router context done in module", "id", t.Id()) t.logger.Debug("router context done in module")
return nil return nil
case time := <-t.timer.C: case time := <-t.timer.C:
if t.router != nil { if t.router != nil {

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
@@ -17,6 +18,7 @@ type UDPClient struct {
conn *net.UDPConn conn *net.UDPConn
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
logger *slog.Logger
} }
func init() { func init() {
@@ -27,24 +29,24 @@ func init() {
host, ok := params["host"] host, ok := params["host"]
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client requires a host parameter") return nil, errors.New("net.udp.client requires a host parameter")
} }
hostString, ok := host.(string) hostString, ok := host.(string)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client host must be a string") return nil, errors.New("net.udp.client host must be a string")
} }
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client requires a port parameter") return nil, errors.New("net.udp.client requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client port must be a number") return nil, errors.New("net.udp.client port must be a number")
} }
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", hostString, uint16(portNum))) addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
@@ -52,7 +54,7 @@ func init() {
return nil, err return nil, err
} }
return &UDPClient{Addr: addr, config: config, ctx: ctx, router: router}, nil return &UDPClient{Addr: addr, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -79,7 +81,7 @@ func (uc *UDPClient) Run() error {
} }
<-uc.ctx.Done() <-uc.ctx.Done()
slog.Debug("router context done in module", "id", uc.Id()) uc.logger.Debug("router context done in module")
if uc.conn != nil { if uc.conn != nil {
uc.conn.Close() uc.conn.Close()
} }
@@ -90,7 +92,7 @@ func (uc *UDPClient) Output(payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return fmt.Errorf("net.udp.client is only able to output bytes") return errors.New("net.udp.client is only able to output bytes")
} }
if uc.conn != nil { if uc.conn != nil {
_, err := uc.conn.Write(payloadBytes) _, err := uc.conn.Write(payloadBytes)
@@ -99,7 +101,7 @@ func (uc *UDPClient) Output(payload any) error {
return err return err
} }
} else { } else {
return fmt.Errorf("net.udp.client client is not setup") return errors.New("net.udp.client client is not setup")
} }
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
@@ -17,6 +18,7 @@ type UDPMulticast struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
Addr *net.UDPAddr Addr *net.UDPAddr
logger *slog.Logger
} }
func init() { func init() {
@@ -27,31 +29,31 @@ func init() {
ip, ok := params["ip"] ip, ok := params["ip"]
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client requires am ip parameter") return nil, errors.New("net.udp.multicast requires an ip parameter")
} }
ipString, ok := ip.(string) ipString, ok := ip.(string)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client ip must be a string") return nil, errors.New("net.udp.multicast ip must be a string")
} }
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client requires a port parameter") return nil, errors.New("net.udp.multicast requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.client port must be a number") return nil, errors.New("net.udp.multicast port must be a number")
} }
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum))) addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &UDPMulticast{config: config, Addr: addr, ctx: ctx, router: router}, nil return &UDPMulticast{config: config, Addr: addr, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -79,7 +81,7 @@ func (um *UDPMulticast) Run() error {
select { select {
case <-um.ctx.Done(): case <-um.ctx.Done():
// TODO(jwetzell): cleanup? // TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", um.Id()) um.logger.Debug("router context done in module")
return nil return nil
default: default:
um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -99,7 +101,7 @@ func (um *UDPMulticast) Run() error {
if um.router != nil { if um.router != nil {
um.router.HandleInput(um.Id(), message) um.router.HandleInput(um.Id(), message)
} else { } else {
slog.Error("net.udp.multicast has no router", "id", um.Id()) um.logger.Error("net.udp.multicast has no router")
} }
} }
} }
@@ -110,11 +112,11 @@ func (um *UDPMulticast) Output(payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return fmt.Errorf("net.udp.multicast can only output bytes") return errors.New("net.udp.multicast can only output bytes")
} }
if um.conn == nil { if um.conn == nil {
return fmt.Errorf("net.udp.multicast connection is not setup") return errors.New("net.udp.multicast connection is not setup")
} }
_, err := um.conn.Write(payloadBytes) _, err := um.conn.Write(payloadBytes)

View File

@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"log/slog" "log/slog"
@@ -13,10 +14,12 @@ import (
) )
type UDPServer struct { type UDPServer struct {
Addr *net.UDPAddr Addr *net.UDPAddr
config config.ModuleConfig BufferSize int
ctx context.Context config config.ModuleConfig
router route.RouteIO ctx context.Context
router route.RouteIO
logger *slog.Logger
} }
func init() { func init() {
@@ -26,13 +29,13 @@ func init() {
params := config.Params params := config.Params
port, ok := params["port"] port, ok := params["port"]
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.server requires a port parameter") return nil, errors.New("net.udp.server requires a port parameter")
} }
portNum, ok := port.(float64) portNum, ok := port.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.server port must be a number") return nil, errors.New("net.udp.server port must be a number")
} }
ipString := "0.0.0.0" ipString := "0.0.0.0"
@@ -43,7 +46,7 @@ func init() {
specificIpString, ok := ip.(string) specificIpString, ok := ip.(string)
if !ok { if !ok {
return nil, fmt.Errorf("net.udp.server ip must be a string") return nil, errors.New("net.udp.server ip must be a string")
} }
ipString = specificIpString ipString = specificIpString
} }
@@ -53,7 +56,19 @@ func init() {
log.Fatalf("error resolving UDP address: %v", err) log.Fatalf("error resolving UDP address: %v", err)
} }
return &UDPServer{Addr: addr, config: config, ctx: ctx, router: router}, nil bufferSizeNum := 2048
bufferSize, ok := params["bufferSize"]
if ok {
bufferSizeFloat, ok := bufferSize.(float64)
if !ok {
return nil, errors.New("net.udp.server bufferSize must be a number")
}
bufferSizeNum = int(bufferSizeFloat)
}
return &UDPServer{Addr: addr, BufferSize: bufferSizeNum, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
}, },
}) })
} }
@@ -75,13 +90,12 @@ func (us *UDPServer) Run() error {
defer listener.Close() defer listener.Close()
// TODO(jwetzell): make buffer size configurable buffer := make([]byte, us.BufferSize)
buffer := make([]byte, 65535)
for { for {
select { select {
case <-us.ctx.Done(): case <-us.ctx.Done():
// TODO(jwetzell): cleanup? // TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", us.Id()) us.logger.Debug("router context done in module")
return nil return nil
default: default:
listener.SetDeadline(time.Now().Add(time.Millisecond * 200)) listener.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -98,7 +112,7 @@ func (us *UDPServer) Run() error {
if us.router != nil { if us.router != nil {
us.router.HandleInput(us.Id(), message) us.router.HandleInput(us.Id(), message)
} else { } else {
slog.Error("net.udp.server has no router", "id", us.Id()) us.logger.Error("net.udp.server has no router")
} }
} }
} }
@@ -106,5 +120,5 @@ func (us *UDPServer) Run() error {
} }
func (us *UDPServer) Output(payload any) error { func (us *UDPServer) Output(payload any) error {
return fmt.Errorf("net.udp.server output is not implemented") return errors.New("net.udp.server output is not implemented")
} }

View File

@@ -10,10 +10,11 @@ import (
type DebugLog struct { type DebugLog struct {
config config.ProcessorConfig config config.ProcessorConfig
logger *slog.Logger
} }
func (dl *DebugLog) Process(ctx context.Context, payload any) (any, error) { func (dl *DebugLog) Process(ctx context.Context, payload any) (any, error) {
slog.Debug("debug.log", "payload", payload, "payloadType", fmt.Sprintf("%T", payload)) dl.logger.Debug("debug.log", "payload", payload, "payloadType", fmt.Sprintf("%T", payload))
return payload, nil return payload, nil
} }
@@ -25,7 +26,7 @@ func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "debug.log", Type: "debug.log",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &DebugLog{config: config}, nil return &DebugLog{config: config, logger: slog.Default().With("component", "processor")}, nil
}, },
}) })
} }

View File

@@ -2,25 +2,25 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"strconv" "strconv"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type FloatParse struct { type FloatParse struct {
config config.ProcessorConfig BitSize int
config config.ProcessorConfig
} }
func (fp *FloatParse) Process(ctx context.Context, payload any) (any, error) { func (fp *FloatParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("float.parse processor only accepts a string") return nil, errors.New("float.parse processor only accepts a string")
} }
// TODO(jwetzell): make bitSize configurable payloadFloat, err := strconv.ParseFloat(payloadString, fp.BitSize)
payloadFloat, err := strconv.ParseFloat(payloadString, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -35,7 +35,19 @@ func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "float.parse", Type: "float.parse",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &FloatParse{config: config}, nil params := config.Params
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("float.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &FloatParse{config: config, BitSize: bitSizeNum}, nil
}, },
}) })
} }

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"strconv" "strconv"
"text/template" "text/template"
@@ -190,13 +190,13 @@ func init() {
id, ok := params["id"] id, ok := params["id"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an id parameter") return nil, errors.New("freed.create requires an id parameter")
} }
idString, ok := id.(string) idString, ok := id.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create id must be a string") return nil, errors.New("freed.create id must be a string")
} }
idTemplate, err := template.New("id").Parse(idString) idTemplate, err := template.New("id").Parse(idString)
@@ -208,13 +208,13 @@ func init() {
pan, ok := params["pan"] pan, ok := params["pan"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an pan parameter") return nil, errors.New("freed.create requires a pan parameter")
} }
panString, ok := pan.(string) panString, ok := pan.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create pan must be a string") return nil, errors.New("freed.create pan must be a string")
} }
panTemplate, err := template.New("pan").Parse(panString) panTemplate, err := template.New("pan").Parse(panString)
@@ -222,13 +222,13 @@ func init() {
tilt, ok := params["tilt"] tilt, ok := params["tilt"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an tilt parameter") return nil, errors.New("freed.create requires a tilt parameter")
} }
tiltString, ok := tilt.(string) tiltString, ok := tilt.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create tilt must be a string") return nil, errors.New("freed.create tilt must be a string")
} }
tiltTemplate, err := template.New("tilt").Parse(tiltString) tiltTemplate, err := template.New("tilt").Parse(tiltString)
@@ -236,13 +236,13 @@ func init() {
roll, ok := params["roll"] roll, ok := params["roll"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an roll parameter") return nil, errors.New("freed.create requires a roll parameter")
} }
rollString, ok := roll.(string) rollString, ok := roll.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create roll must be a string") return nil, errors.New("freed.create roll must be a string")
} }
rollTemplate, err := template.New("roll").Parse(rollString) rollTemplate, err := template.New("roll").Parse(rollString)
@@ -254,13 +254,13 @@ func init() {
posX, ok := params["posX"] posX, ok := params["posX"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires a posX parameter") return nil, errors.New("freed.create requires a posX parameter")
} }
posXString, ok := posX.(string) posXString, ok := posX.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create posX must be a string") return nil, errors.New("freed.create posX must be a string")
} }
posXTemplate, err := template.New("posX").Parse(posXString) posXTemplate, err := template.New("posX").Parse(posXString)
@@ -272,13 +272,13 @@ func init() {
posY, ok := params["posY"] posY, ok := params["posY"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires a posY parameter") return nil, errors.New("freed.create requires a posY parameter")
} }
posYString, ok := posY.(string) posYString, ok := posY.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create posY must be a string") return nil, errors.New("freed.create posY must be a string")
} }
posYTemplate, err := template.New("posY").Parse(posYString) posYTemplate, err := template.New("posY").Parse(posYString)
@@ -290,13 +290,13 @@ func init() {
posZ, ok := params["posZ"] posZ, ok := params["posZ"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires a posZ parameter") return nil, errors.New("freed.create requires a posZ parameter")
} }
posZString, ok := posZ.(string) posZString, ok := posZ.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create posZ must be a string") return nil, errors.New("freed.create posZ must be a string")
} }
posZTemplate, err := template.New("posZ").Parse(posZString) posZTemplate, err := template.New("posZ").Parse(posZString)
@@ -308,13 +308,13 @@ func init() {
zoom, ok := params["zoom"] zoom, ok := params["zoom"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an zoom parameter") return nil, errors.New("freed.create requires a zoom parameter")
} }
zoomString, ok := zoom.(string) zoomString, ok := zoom.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create zoom must be a string") return nil, errors.New("freed.create zoom must be a string")
} }
zoomTemplate, err := template.New("zoom").Parse(zoomString) zoomTemplate, err := template.New("zoom").Parse(zoomString)
@@ -322,13 +322,13 @@ func init() {
focus, ok := params["focus"] focus, ok := params["focus"]
if !ok { if !ok {
return nil, fmt.Errorf("freed.create requires an focus parameter") return nil, errors.New("freed.create requires a focus parameter")
} }
focusString, ok := focus.(string) focusString, ok := focus.(string)
if !ok { if !ok {
return nil, fmt.Errorf("freed.create focus must be a string") return nil, errors.New("freed.create focus must be a string")
} }
focusTemplate, err := template.New("focus").Parse(focusString) focusTemplate, err := template.New("focus").Parse(focusString)

View File

@@ -2,8 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"log/slog"
freeD "github.com/jwetzell/free-d-go" freeD "github.com/jwetzell/free-d-go"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -17,12 +16,12 @@ func (fdd *FreeDDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return nil, fmt.Errorf("freed.decode processor only accepts a []byte") return nil, errors.New("freed.decode processor only accepts a []byte")
} }
payloadMessage, err := freeD.Decode(payloadBytes) payloadMessage, err := freeD.Decode(payloadBytes)
if err != nil { if err != nil {
slog.Error("error decoding", "err", err) return nil, err
} }
return payloadMessage, nil return payloadMessage, nil
} }

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
freeD "github.com/jwetzell/free-d-go" freeD "github.com/jwetzell/free-d-go"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (fde *FreeDEncode) Process(ctx context.Context, payload any) (any, error) {
payloadPosition, ok := payload.(freeD.FreeDPosition) payloadPosition, ok := payload.(freeD.FreeDPosition)
if !ok { if !ok {
return nil, fmt.Errorf("freed.decode processor only accepts a FreeDEncode") return nil, errors.New("freed.decode processor only accepts a FreeDEncode")
} }
payloadBytes := freeD.Encode(payloadPosition) payloadBytes := freeD.Encode(payloadPosition)

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"net/http" "net/http"
"text/template" "text/template"
@@ -16,10 +16,10 @@ type HTTPRequestCreate struct {
URL *template.Template URL *template.Template
} }
func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, error) { func (hrc *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, error) {
var urlBuffer bytes.Buffer var urlBuffer bytes.Buffer
err := hre.URL.Execute(&urlBuffer, payload) err := hrc.URL.Execute(&urlBuffer, payload)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -28,7 +28,7 @@ func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, er
urlString := urlBuffer.String() urlString := urlBuffer.String()
//TODO(jwetzell): support body //TODO(jwetzell): support body
request, err := http.NewRequest(hre.Method, urlString, bytes.NewBuffer([]byte{})) request, err := http.NewRequest(hrc.Method, urlString, bytes.NewBuffer([]byte{}))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -37,8 +37,8 @@ func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, er
return request, nil return request, nil
} }
func (hre *HTTPRequestCreate) Type() string { func (hrc *HTTPRequestCreate) Type() string {
return hre.config.Type return hrc.config.Type
} }
func init() { func init() {
@@ -50,25 +50,25 @@ func init() {
method, ok := params["method"] method, ok := params["method"]
if !ok { if !ok {
return nil, fmt.Errorf("http.request.create requires an method parameter") return nil, errors.New("http.request.create requires a method parameter")
} }
methodString, ok := method.(string) methodString, ok := method.(string)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.create url must be a string") return nil, errors.New("http.request.create url must be a string")
} }
url, ok := params["url"] url, ok := params["url"]
if !ok { if !ok {
return nil, fmt.Errorf("http.request.create requires an url parameter") return nil, errors.New("http.request.create requires a url parameter")
} }
urlString, ok := url.(string) urlString, ok := url.(string)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.create url must be a string") return nil, errors.New("http.request.create url must be a string")
} }
urlTemplate, err := template.New("url").Parse(urlString) urlTemplate, err := template.New("url").Parse(urlString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"io" "io"
"net/http" "net/http"
@@ -17,7 +17,7 @@ func (hre *HTTPRequestEncode) Process(ctx context.Context, payload any) (any, er
payloadRequest, ok := payload.(*http.Request) payloadRequest, ok := payload.(*http.Request)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.encode processor only accepts an http.Request") return nil, errors.New("http.request.encode processor only accepts an http.Request")
} }
bytes, err := io.ReadAll(payloadRequest.Body) bytes, err := io.ReadAll(payloadRequest.Body)

View File

@@ -2,6 +2,7 @@ package processor
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
@@ -20,7 +21,7 @@ func (hrf *HTTPRequestFilter) Process(ctx context.Context, payload any) (any, er
payloadRequest, ok := payload.(*http.Request) payloadRequest, ok := payload.(*http.Request)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter can only operate on http.Request payloads") return nil, errors.New("http.request.filter can only operate on http.Request payloads")
} }
if hrf.Method != "" { if hrf.Method != "" {
@@ -48,13 +49,13 @@ func init() {
path, ok := params["path"] path, ok := params["path"]
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter requires an path parameter") return nil, errors.New("http.request.filter requires a path parameter")
} }
pathString, ok := path.(string) pathString, ok := path.(string)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter path must be a string") return nil, errors.New("http.request.filter path must be a string")
} }
pathRegexp, err := regexp.Compile(fmt.Sprintf("^%s$", pathString)) pathRegexp, err := regexp.Compile(fmt.Sprintf("^%s$", pathString))
@@ -69,7 +70,7 @@ func init() {
methodString, ok := method.(string) methodString, ok := method.(string)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter method must be a string") return nil, errors.New("http.request.filter method must be a string")
} }
return &HTTPRequestFilter{config: config, Path: pathRegexp, Method: methodString}, nil return &HTTPRequestFilter{config: config, Path: pathRegexp, Method: methodString}, nil
} }

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"io" "io"
"net/http" "net/http"
@@ -17,7 +17,7 @@ func (hre *HTTPResponseEncode) Process(ctx context.Context, payload any) (any, e
payloadResponse, ok := payload.(*http.Response) payloadResponse, ok := payload.(*http.Response)
if !ok { if !ok {
return nil, fmt.Errorf("http.response.encode processor only accepts an http.Response") return nil, errors.New("http.response.encode processor only accepts an http.Response")
} }
defer payloadResponse.Body.Close() defer payloadResponse.Body.Close()

View File

@@ -2,25 +2,26 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"strconv" "strconv"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type IntParse struct { type IntParse struct {
config config.ProcessorConfig Base int
BitSize int
config config.ProcessorConfig
} }
func (ip *IntParse) Process(ctx context.Context, payload any) (any, error) { func (ip *IntParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("int.parse processor only accepts a string") return nil, errors.New("int.parse processor only accepts a string")
} }
// TODO(jwetzell): make base and bitSize configurable payloadInt, err := strconv.ParseInt(payloadString, ip.Base, ip.BitSize)
payloadInt, err := strconv.ParseInt(payloadString, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -35,7 +36,32 @@ func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "int.parse", Type: "int.parse",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &IntParse{config: config}, nil params := config.Params
baseNum := 10
base, ok := params["base"]
if ok {
baseFloat, ok := base.(float64)
if !ok {
return nil, errors.New("int.parse base must be a number")
}
baseNum = int(baseFloat)
}
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("int.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &IntParse{config: config, Base: baseNum, BitSize: bitSizeNum}, nil
}, },
}) })
} }

View File

@@ -0,0 +1,376 @@
//go:build cgo
package processor
import (
"bytes"
"context"
"errors"
"fmt"
"strconv"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2"
)
// TODO(jwetzell): support using numbers in config file treated as hardcoded values
type MIDIMessageCreate struct {
config config.ProcessorConfig
ProcessFunc func(ctx context.Context, payload any) (any, error)
}
func (mmd *MIDIMessageCreate) Process(ctx context.Context, payload any) (any, error) {
return mmd.ProcessFunc(ctx, payload)
}
func (mmd *MIDIMessageCreate) Type() string {
return mmd.config.Type
}
func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, errors.New("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
note, ok := params["note"]
if !ok {
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, errors.New("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
if err != nil {
return nil, err
}
velocity, ok := params["velocity"]
if !ok {
return nil, errors.New("midi.message.create NoteOn requires a velocity parameter")
}
velocityString, ok := velocity.(string)
if !ok {
return nil, errors.New("midi.message.create NoteOn velocity must be a string")
}
velocityTemplate, err := template.New("velocity").Parse(velocityString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
if err != nil {
return nil, err
}
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
var velocityBuffer bytes.Buffer
err = velocityTemplate.Execute(&velocityBuffer, payload)
if err != nil {
return nil, err
}
velocityValue, err := strconv.ParseUint(velocityBuffer.String(), 10, 8)
payloadMessage := midi.NoteOn(uint8(channelValue), uint8(noteValue), uint8(velocityValue))
return payloadMessage, nil
}}, nil
}
func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, errors.New("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
note, ok := params["note"]
if !ok {
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, errors.New("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
if err != nil {
return nil, err
}
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
payloadMessage := midi.NoteOff(uint8(channelValue), uint8(noteValue))
return payloadMessage, nil
}}, nil
}
func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, errors.New("midi.message.create ControlChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, errors.New("midi.message.create ControlChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
controller, ok := params["controller"]
if !ok {
return nil, errors.New("midi.message.create ControlChange requires a controller parameter")
}
controllerString, ok := controller.(string)
if !ok {
return nil, errors.New("midi.message.create ControlChange controller must be a string")
}
controllerTemplate, err := template.New("controller").Parse(controllerString)
if err != nil {
return nil, err
}
value, ok := params["value"]
if !ok {
return nil, errors.New("midi.message.create ControlChange requires a value parameter")
}
valueString, ok := value.(string)
if !ok {
return nil, errors.New("midi.message.create ControlChange value must be a string")
}
valueTemplate, err := template.New("value").Parse(valueString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var controllerBuffer bytes.Buffer
err = controllerTemplate.Execute(&controllerBuffer, payload)
if err != nil {
return nil, err
}
controllerValue, err := strconv.ParseUint(controllerBuffer.String(), 10, 8)
var valueBuffer bytes.Buffer
err = valueTemplate.Execute(&valueBuffer, payload)
if err != nil {
return nil, err
}
valueValue, err := strconv.ParseUint(valueBuffer.String(), 10, 8)
payloadMessage := midi.ControlChange(uint8(channelValue), uint8(controllerValue), uint8(valueValue))
return payloadMessage, nil
}}, nil
}
func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, errors.New("midi.message.create ProgramChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, errors.New("midi.message.create ProgramChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
program, ok := params["program"]
if !ok {
return nil, errors.New("midi.message.create ProgramChange requires a program parameter")
}
programString, ok := program.(string)
if !ok {
return nil, errors.New("midi.message.create ProgramChange program must be a string")
}
programTemplate, err := template.New("program").Parse(programString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var programBuffer bytes.Buffer
err = programTemplate.Execute(&programBuffer, payload)
if err != nil {
return nil, err
}
programValue, err := strconv.ParseUint(programBuffer.String(), 10, 8)
payloadMessage := midi.ProgramChange(uint8(channelValue), uint8(programValue))
return payloadMessage, nil
}}, nil
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "midi.message.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
msgType, ok := params["type"]
if !ok {
return nil, errors.New("midi.message.create requires a type parameter")
}
msgTypeString, ok := msgType.(string)
if !ok {
return nil, errors.New("midi.message.create type parameter must be a string")
}
switch msgTypeString {
case "NoteOn", "noteon", "note_on":
return newMidiNoteOnCreate(config)
case "NoteOff", "noteoff", "note_off":
return newMidiNoteOffCreate(config)
case "ControlChange", "controlchange", "control_change":
return newMidiControlChangeCreate(config)
case "ProgramChange", "programchange", "program_change":
return newMidiProgramChangeCreate(config)
default:
return nil, fmt.Errorf("midi.message.create does not support type %s", msgTypeString)
}
},
})
}

View File

@@ -4,7 +4,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
@@ -18,7 +18,7 @@ func (mmd *MIDIMessageDecode) Process(ctx context.Context, payload any) (any, er
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.decode processor only accepts a []byte") return nil, errors.New("midi.message.decode processor only accepts a []byte")
} }
payloadMessage := midi.Message(payloadBytes) payloadMessage := midi.Message(payloadBytes)

View File

@@ -4,7 +4,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
@@ -18,7 +18,7 @@ func (mme *MIDIMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(midi.Message) payloadMessage, ok := payload.(midi.Message)
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.encode processor only accepts an midi.Message") return nil, errors.New("midi.message.encode processor only accepts an midi.Message")
} }
return payloadMessage.Bytes(), nil return payloadMessage.Bytes(), nil

View File

@@ -4,7 +4,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
@@ -19,7 +19,7 @@ func (mmf *MIDIMessageFilter) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(midi.Message) payloadMessage, ok := payload.(midi.Message)
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.filter processor only accepts an midi.Message") return nil, errors.New("midi.message.filter processor only accepts an midi.Message")
} }
if payloadMessage.Type().String() != mmf.MIDIType { if payloadMessage.Type().String() != mmf.MIDIType {
@@ -41,12 +41,12 @@ func init() {
midiType, ok := params["type"] midiType, ok := params["type"]
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.filter requires a type parameter") return nil, errors.New("midi.message.filter requires a type parameter")
} }
midiTypeString, ok := midiType.(string) midiTypeString, ok := midiType.(string)
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.filter type must be a string") return nil, errors.New("midi.message.filter type must be a string")
} }
return &MIDIMessageFilter{config: config, MIDIType: midiTypeString}, nil return &MIDIMessageFilter{config: config, MIDIType: midiTypeString}, nil

View File

@@ -4,6 +4,7 @@ package processor
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -47,7 +48,7 @@ func (mmu *MIDIMessageUnpack) Process(ctx context.Context, payload any) (any, er
payloadMidi, ok := payload.(midi.Message) payloadMidi, ok := payload.(midi.Message)
if !ok { if !ok {
return nil, fmt.Errorf("midi.message.unpack processor only accepts a midi.Message") return nil, errors.New("midi.message.unpack processor only accepts a midi.Message")
} }
switch payloadMidi.Type() { switch payloadMidi.Type() {

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
@@ -74,44 +74,44 @@ func init() {
topic, ok := params["topic"] topic, ok := params["topic"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an topic parameter") return nil, errors.New("mqtt.message.create requires a topic parameter")
} }
topicString, ok := topic.(string) topicString, ok := topic.(string)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create topic must be a string") return nil, errors.New("mqtt.message.create topic must be a string")
} }
qos, ok := params["qos"] qos, ok := params["qos"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an qos parameter") return nil, errors.New("mqtt.message.create requires a qos parameter")
} }
qosByte, ok := qos.(float64) qosByte, ok := qos.(float64)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create qos must be a number") return nil, errors.New("mqtt.message.create qos must be a number")
} }
retained, ok := params["retained"] retained, ok := params["retained"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an retained parameter") return nil, errors.New("mqtt.message.create requires a retained parameter")
} }
retainedBool, ok := retained.(bool) retainedBool, ok := retained.(bool)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create retained must be a boolean") return nil, errors.New("mqtt.message.create retained must be a boolean")
} }
//TODO(jwetzell): convert payload into []byte or string for sending //TODO(jwetzell): convert payload into []byte or string for sending
payload, ok := params["payload"] payload, ok := params["payload"]
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an payload parameter") return nil, errors.New("mqtt.message.create requires a payload parameter")
} }
if payloadBytes, ok := payload.([]byte); ok { if payloadBytes, ok := payload.([]byte); ok {
@@ -121,7 +121,7 @@ func init() {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.create payload must be a string or byte array") return nil, errors.New("mqtt.message.create payload must be a string or byte array")
} }
payloadBytes := []byte(payloadString) payloadBytes := []byte(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (mme *MQTTMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(mqtt.Message) payloadMessage, ok := payload.(mqtt.Message)
if !ok { if !ok {
return nil, fmt.Errorf("mqtt.message.encode processor only accepts an mqtt.Message") return nil, errors.New("mqtt.message.encode processor only accepts an mqtt.Message")
} }
return payloadMessage.Payload(), nil return payloadMessage.Payload(), nil

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"text/template" "text/template"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -52,25 +52,25 @@ func init() {
subject, ok := params["subject"] subject, ok := params["subject"]
if !ok { if !ok {
return nil, fmt.Errorf("nats.message.create requires a subject parameter") return nil, errors.New("nats.message.create requires a subject parameter")
} }
subjectString, ok := subject.(string) subjectString, ok := subject.(string)
if !ok { if !ok {
return nil, fmt.Errorf("nats.message.create subject must be a string") return nil, errors.New("nats.message.create subject must be a string")
} }
payload, ok := params["payload"] payload, ok := params["payload"]
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create requires a payload parameter") return nil, errors.New("nats.message.create requires a payload parameter")
} }
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create payload must be a string") return nil, errors.New("nats.message.create payload must be a string")
} }
payloadTemplate, err := template.New("payload").Parse(payloadString) payloadTemplate, err := template.New("payload").Parse(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
@@ -16,7 +16,7 @@ func (nme *NATSMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(*nats.Msg) payloadMessage, ok := payload.(*nats.Msg)
if !ok { if !ok {
return nil, fmt.Errorf("nats.message.encode processor only accepts an nats.Msg") return nil, errors.New("nats.message.encode processor only accepts an nats.Msg")
} }
return payloadMessage.Data, nil return payloadMessage.Data, nil

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"text/template" "text/template"
@@ -31,11 +32,11 @@ func (o *OSCMessageCreate) Process(ctx context.Context, payload any) (any, error
addressString := addressBuffer.String() addressString := addressBuffer.String()
if len(addressString) == 0 { if len(addressString) == 0 {
return nil, fmt.Errorf("osc.message.create address must not be empty") return nil, errors.New("osc.message.create address must not be empty")
} }
if addressString[0] != '/' { if addressString[0] != '/' {
return nil, fmt.Errorf("osc.message.create address must start with '/'") return nil, errors.New("osc.message.create address must start with '/'")
} }
payloadMessage := osc.OSCMessage{ payloadMessage := osc.OSCMessage{
@@ -82,13 +83,13 @@ func init() {
address, ok := params["address"] address, ok := params["address"]
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create requires an address parameter") return nil, errors.New("osc.message.create requires an address parameter")
} }
addressString, ok := address.(string) addressString, ok := address.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create address must be a string") return nil, errors.New("osc.message.create address must be a string")
} }
addressTemplate, err := template.New("address").Parse(addressString) addressTemplate, err := template.New("address").Parse(addressString)
@@ -109,17 +110,17 @@ func init() {
types, ok := params["types"] types, ok := params["types"]
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create requires a types parameter with args") return nil, errors.New("osc.message.create requires a types parameter with args")
} }
typesString, ok := types.(string) typesString, ok := types.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create types must be a string") return nil, errors.New("osc.message.create types must be a string")
} }
if len(rawArgs) != len(typesString) { if len(rawArgs) != len(typesString) {
return nil, fmt.Errorf("osc.message.create args and types must be the same length") return nil, errors.New("osc.message.create args and types must be the same length")
} }
argTemplates := []*template.Template{} argTemplates := []*template.Template{}
@@ -128,7 +129,7 @@ func init() {
argString, ok := rawArg.(string) argString, ok := rawArg.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.create arg must be a string") return nil, errors.New("osc.message.create arg must be a string")
} }
argTemplate, err := template.New("arg").Parse(argString) argTemplate, err := template.New("arg").Parse(argString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
osc "github.com/jwetzell/osc-go" osc "github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -16,15 +16,15 @@ func (o *OSCMessageDecode) Process(ctx context.Context, payload any) (any, error
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.decode processor only accepts a []byte payload") return nil, errors.New("osc.message.decode processor only accepts a []byte payload")
} }
if len(payloadBytes) == 0 { if len(payloadBytes) == 0 {
return nil, fmt.Errorf("osc.message.decode processor can't work on empty []byte") return nil, errors.New("osc.message.decode processor can't work on empty []byte")
} }
if payloadBytes[0] != '/' { if payloadBytes[0] != '/' {
return nil, fmt.Errorf("osc.message.decode processor needs an OSC looking []byte") return nil, errors.New("osc.message.decode processor needs an OSC looking []byte")
} }
message, err := osc.MessageFromBytes(payloadBytes) message, err := osc.MessageFromBytes(payloadBytes)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
osc "github.com/jwetzell/osc-go" osc "github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (o *OSCMessageEncode) Process(ctx context.Context, payload any) (any, error
payloadMessage, ok := payload.(osc.OSCMessage) payloadMessage, ok := payload.(osc.OSCMessage)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.encode processor only accepts an OSCMessage") return nil, errors.New("osc.message.encode processor only accepts an OSCMessage")
} }
bytes := payloadMessage.ToBytes() bytes := payloadMessage.ToBytes()

View File

@@ -2,6 +2,7 @@ package processor
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
@@ -20,7 +21,7 @@ func (o *OSCMessageFilter) Process(ctx context.Context, payload any) (any, error
payloadMessage, ok := payload.(osc.OSCMessage) payloadMessage, ok := payload.(osc.OSCMessage)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.filter can only operate on OSCMessage payloads") return nil, errors.New("osc.message.filter can only operate on OSCMessage payloads")
} }
if !o.Address.MatchString(payloadMessage.Address) { if !o.Address.MatchString(payloadMessage.Address) {
@@ -42,13 +43,13 @@ func init() {
address, ok := params["address"] address, ok := params["address"]
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.filter requires an address parameter") return nil, errors.New("osc.message.filter requires an address parameter")
} }
addressString, ok := address.(string) addressString, ok := address.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.filter address must be a string") return nil, errors.New("osc.message.filter address must be a string")
} }
addressPattern := strings.ReplaceAll(addressString, "?", ".") addressPattern := strings.ReplaceAll(addressString, "?", ".")

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"text/template" "text/template"
osc "github.com/jwetzell/osc-go" osc "github.com/jwetzell/osc-go"
@@ -19,11 +19,10 @@ func (o *OSCMessageTransform) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(osc.OSCMessage) payloadMessage, ok := payload.(osc.OSCMessage)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.transform processor only accepts an OSCMessage") return nil, errors.New("osc.message.transform processor only accepts an OSCMessage")
} }
var addressBuffer bytes.Buffer var addressBuffer bytes.Buffer
//TODO(jwetzell): actually inject data into template
err := o.Address.Execute(&addressBuffer, payloadMessage) err := o.Address.Execute(&addressBuffer, payloadMessage)
if err != nil { if err != nil {
@@ -33,11 +32,11 @@ func (o *OSCMessageTransform) Process(ctx context.Context, payload any) (any, er
addressString := addressBuffer.String() addressString := addressBuffer.String()
if len(addressString) == 0 { if len(addressString) == 0 {
return nil, fmt.Errorf("osc.message.transform address must not be empty") return nil, errors.New("osc.message.transform address must not be empty")
} }
if addressString[0] != '/' { if addressString[0] != '/' {
return nil, fmt.Errorf("osc.message.transform address must start with '/'") return nil, errors.New("osc.message.transform address must start with '/'")
} }
payloadMessage.Address = addressString payloadMessage.Address = addressString
@@ -57,13 +56,13 @@ func init() {
address, ok := params["address"] address, ok := params["address"]
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.transform requires an address parameter") return nil, errors.New("osc.message.transform requires an address parameter")
} }
addressString, ok := address.(string) addressString, ok := address.(string)
if !ok { if !ok {
return nil, fmt.Errorf("osc.message.transform address must be a string") return nil, errors.New("osc.message.transform address must be a string")
} }
addressTemplate, err := template.New("address").Parse(addressString) addressTemplate, err := template.New("address").Parse(addressString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/expr-lang/expr" "github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm" "github.com/expr-lang/expr/vm"
@@ -38,13 +38,13 @@ func init() {
expression, ok := params["expression"] expression, ok := params["expression"]
if !ok { if !ok {
return nil, fmt.Errorf("script.expr requires an expression parameter") return nil, errors.New("script.expr requires an expression parameter")
} }
expressionString, ok := expression.(string) expressionString, ok := expression.(string)
if !ok { if !ok {
return nil, fmt.Errorf("script.expr expression must be a string") return nil, errors.New("script.expr expression must be a string")
} }
program, err := expr.Compile(expressionString) program, err := expr.Compile(expressionString)

View File

@@ -0,0 +1,58 @@
package processor_test
import (
"testing"
"github.com/expr-lang/expr"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestGoodScriptExpr(t *testing.T) {
tests := []struct {
program string
name string
payload map[string]any
expected any
}{
{
program: "foo + bar",
name: "number",
payload: map[string]any{
"foo": 1,
"bar": 1,
},
expected: 2,
},
{
program: "foo + bar",
name: "string",
payload: map[string]any{
"foo": "1",
"bar": "1",
},
expected: "11",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
program, err := expr.Compile(test.program)
if err != nil {
t.Errorf("script.expr failed to compile program: %s", err)
}
exprProcessor := &processor.ScriptExpr{Program: program}
got, err := exprProcessor.Process(t.Context(), test.payload)
if err != nil {
t.Errorf("script.expr failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if got != test.expected {
t.Errorf("script.expr got %+v (%T), expected %+v (%T)", got, got, test.expected, test.expected)
}
})
}
}

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"modernc.org/quickjs" "modernc.org/quickjs"
@@ -74,13 +74,13 @@ func init() {
program, ok := params["program"] program, ok := params["program"]
if !ok { if !ok {
return nil, fmt.Errorf("script.js requires a program parameter") return nil, errors.New("script.js requires a program parameter")
} }
programString, ok := program.(string) programString, ok := program.(string)
if !ok { if !ok {
return nil, fmt.Errorf("script.js program must be a string") return nil, errors.New("script.js program must be a string")
} }
return &ScriptJS{config: config, Program: programString}, nil return &ScriptJS{config: config, Program: programString}, nil

View File

@@ -0,0 +1,48 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestGoodScriptJS(t *testing.T) {
tests := []struct {
processor processor.Processor
name string
payload any
expected any
}{
{
processor: &processor.ScriptJS{Program: `
payload = payload + 1
`},
name: "number",
payload: 1,
expected: 2,
},
{
processor: &processor.ScriptJS{Program: `
payload = payload + "1"
`},
name: "string",
payload: "1",
expected: "11",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := test.processor.Process(t.Context(), test.payload)
if err != nil {
t.Errorf("script.js failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if got != test.expected {
t.Errorf("script.js got %+v, expected %+v", got, test.expected)
}
})
}
}

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"text/template" "text/template"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -39,13 +39,13 @@ func init() {
tmpl, ok := params["template"] tmpl, ok := params["template"]
if !ok { if !ok {
return nil, fmt.Errorf("string.create requires a template parameter") return nil, errors.New("string.create requires a template parameter")
} }
templateString, ok := tmpl.(string) templateString, ok := tmpl.(string)
if !ok { if !ok {
return nil, fmt.Errorf("string.create template must be a string") return nil, errors.New("string.create template must be a string")
} }
templateTemplate, err := template.New("template").Parse(templateString) templateTemplate, err := template.New("template").Parse(templateString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
@@ -15,7 +15,7 @@ func (sd *StringDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return nil, fmt.Errorf("string.decode processor only accepts a []byte") return nil, errors.New("string.decode processor only accepts a []byte")
} }
payloadMessage := string(payloadBytes) payloadMessage := string(payloadBytes)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
@@ -15,7 +15,7 @@ func (se *StringEncode) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("string.encode processor only accepts a string") return nil, errors.New("string.encode processor only accepts a string")
} }
payloadBytes := []byte(payloadString) payloadBytes := []byte(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"regexp" "regexp"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -17,7 +17,7 @@ func (se *StringFilter) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("string.filter processor only accepts a string") return nil, errors.New("string.filter processor only accepts a string")
} }
if !se.Pattern.MatchString(payloadString) { if !se.Pattern.MatchString(payloadString) {
@@ -40,16 +40,16 @@ func init() {
pattern, ok := params["pattern"] pattern, ok := params["pattern"]
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter requires an pattern parameter") return nil, errors.New("string.filter requires a pattern parameter")
} }
patternString, ok := pattern.(string) patternString, ok := pattern.(string)
if !ok { if !ok {
return nil, fmt.Errorf("http.request.filter pattern must be a string") return nil, errors.New("string.filter pattern must be a string")
} }
patternRegexp, err := regexp.Compile(fmt.Sprintf("^%s$", patternString)) patternRegexp, err := regexp.Compile(patternString)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"strings" "strings"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -17,7 +17,7 @@ func (se *StringSplit) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("string.split only accepts a string") return nil, errors.New("string.split only accepts a string")
} }
payloadParts := strings.Split(payloadString, se.Separator) payloadParts := strings.Split(payloadString, se.Separator)
@@ -38,13 +38,13 @@ func init() {
separator, ok := params["separator"] separator, ok := params["separator"]
if !ok { if !ok {
return nil, fmt.Errorf("string.split requires a separator") return nil, errors.New("string.split requires a separator")
} }
separatorString, ok := separator.(string) separatorString, ok := separator.(string)
if !ok { if !ok {
return nil, fmt.Errorf("string.split separator must be a string") return nil, errors.New("string.split separator must be a string")
} }
return &StringSplit{config: config, Separator: separatorString}, nil return &StringSplit{config: config, Separator: separatorString}, nil

View File

@@ -40,7 +40,7 @@ func TestGoodStringSplit(t *testing.T) {
} }
} }
func TestBasStringSplit(t *testing.T) { func TestBadStringSplit(t *testing.T) {
tests := []struct { tests := []struct {
processor processor.Processor processor processor.Processor
name string name string

View File

@@ -2,25 +2,26 @@ package processor
import ( import (
"context" "context"
"fmt" "errors"
"strconv" "strconv"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type UintParse struct { type UintParse struct {
config config.ProcessorConfig Base int
BitSize int
config config.ProcessorConfig
} }
func (up *UintParse) Process(ctx context.Context, payload any) (any, error) { func (up *UintParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string) payloadString, ok := payload.(string)
if !ok { if !ok {
return nil, fmt.Errorf("uint.parse processor only accepts a string") return nil, errors.New("uint.parse processor only accepts a string")
} }
// TODO(jwetzell): make base and bitSize configurable payloadUint, err := strconv.ParseUint(payloadString, up.Base, up.BitSize)
payloadUint, err := strconv.ParseUint(payloadString, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -35,7 +36,31 @@ func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "uint.parse", Type: "uint.parse",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &UintParse{config: config}, nil params := config.Params
baseNum := 10
base, ok := params["base"]
if ok {
baseFloat, ok := base.(float64)
if !ok {
return nil, errors.New("uint.parse base must be a number")
}
baseNum = int(baseFloat)
}
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("uint.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &UintParse{config: config, Base: baseNum, BitSize: bitSizeNum}, nil
}, },
}) })
} }

View File

@@ -2,9 +2,9 @@ package showbridge
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"sync" "sync"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
@@ -18,16 +18,10 @@ type Router struct {
ModuleInstances []module.Module ModuleInstances []module.Module
RouteInstances []route.Route RouteInstances []route.Route
moduleWait sync.WaitGroup moduleWait sync.WaitGroup
logger *slog.Logger
} }
func NewRouter(ctx context.Context, config config.Config) (*Router, []module.ModuleError, []route.RouteError) { func NewRouter(ctx context.Context, config config.Config) (*Router, []module.ModuleError, []route.RouteError) {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
slog.Debug("creating router")
routerContext, cancel := context.WithCancel(ctx) routerContext, cancel := context.WithCancel(ctx)
router := Router{ router := Router{
@@ -35,8 +29,11 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
contextCancel: cancel, contextCancel: cancel,
ModuleInstances: []module.Module{}, ModuleInstances: []module.Module{},
RouteInstances: []route.Route{}, RouteInstances: []route.Route{},
logger: slog.Default().With("component", "router"),
} }
router.logger.Debug("creating router")
var moduleErrors []module.ModuleError var moduleErrors []module.ModuleError
for moduleIndex, moduleDecl := range config.Modules { for moduleIndex, moduleDecl := range config.Modules {
@@ -49,7 +46,7 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
moduleErrors = append(moduleErrors, module.ModuleError{ moduleErrors = append(moduleErrors, module.ModuleError{
Index: moduleIndex, Index: moduleIndex,
Config: moduleDecl, Config: moduleDecl,
Error: fmt.Errorf("module type not defined"), Error: errors.New("module type not defined"),
}) })
continue continue
} }
@@ -64,7 +61,7 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
moduleErrors = append(moduleErrors, module.ModuleError{ moduleErrors = append(moduleErrors, module.ModuleError{
Index: moduleIndex, Index: moduleIndex,
Config: moduleDecl, Config: moduleDecl,
Error: fmt.Errorf("duplicate module id"), Error: errors.New("duplicate module id"),
}) })
break break
} }
@@ -110,20 +107,20 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
} }
func (r *Router) Run() { func (r *Router) Run() {
slog.Info("running router") r.logger.Info("running router")
for _, moduleInstance := range r.ModuleInstances { for _, moduleInstance := range r.ModuleInstances {
r.moduleWait.Add(1) r.moduleWait.Add(1)
go func() { go func() {
err := moduleInstance.Run() err := moduleInstance.Run()
if err != nil { if err != nil {
slog.Error("error encountered running module", "id", moduleInstance.Id(), "error", err) r.logger.Error("error encountered running module", "error", err)
} }
r.moduleWait.Done() r.moduleWait.Done()
}() }()
} }
<-r.Context.Done() <-r.Context.Done()
r.moduleWait.Wait() r.moduleWait.Wait()
slog.Info("router done") r.logger.Info("router done")
} }
func (r *Router) Stop() { func (r *Router) Stop() {
@@ -143,7 +140,7 @@ func (r *Router) HandleInput(sourceId string, payload any) []route.RouteIOError
Index: routeIndex, Index: routeIndex,
Error: err, Error: err,
}) })
slog.Error("router unable to route input", "route", routeIndex, "source", sourceId, "error", err) r.logger.Error("router unable to route input", "route", routeIndex, "source", sourceId, "error", err)
} }
} }
} }