mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-04-30 06:45:30 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
168265f0a9 | ||
|
|
caee7b269c | ||
|
|
b0cc47236f | ||
|
|
f6c2b1d9ac | ||
|
|
f977b845be | ||
|
|
0166383978 | ||
|
|
66cfaa3091 | ||
|
|
0904d2fcb8 | ||
|
|
6a13c38e77 | ||
|
|
1a8ccfc64a | ||
|
|
2e3bb408c3 | ||
|
|
3d75165a61 | ||
|
|
50f755f914 | ||
|
|
b10e296d0a | ||
|
|
91c44420cb | ||
|
|
c1b2fa714e | ||
|
|
ff1949ce69 | ||
|
|
76583b1fb8 | ||
|
|
e6433878b5 | ||
|
|
3ad067cbb8 | ||
|
|
30b406d601 | ||
|
|
7315d40015 | ||
|
|
e7105b8b39 | ||
|
|
ec40194ecb | ||
|
|
85d8dc5787 | ||
|
|
b27bfb1a6d | ||
|
|
fb9c0bc657 | ||
|
|
d32d23041b | ||
|
|
b4149df00a | ||
|
|
61bd4b64f5 | ||
|
|
38d73881c9 | ||
|
|
3138bdfcdb | ||
|
|
ba862300b2 | ||
|
|
e414bf336e | ||
|
|
da95fb5c75 | ||
|
|
1fdc30aa9e | ||
|
|
128920dff9 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Dockerfile
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
39
.github/workflows/release-showbridge.yaml
vendored
39
.github/workflows/release-showbridge.yaml
vendored
@@ -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
|
||||||
|
|||||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -10,7 +10,9 @@
|
|||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
"program": "cmd/showbridge"
|
"program": "cmd/showbridge",
|
||||||
|
"args": ["--debug"],
|
||||||
|
"cwd": "./"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
15
Dockerfile
Normal file
15
Dockerfile
Normal 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" ]
|
||||||
@@ -22,8 +22,9 @@ 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)
|
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
|
||||||
- call server
|
- call server
|
||||||
- [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) server
|
- [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) server
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -20,30 +20,66 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
cmd := &cli.Command{
|
cmd := &cli.Command{
|
||||||
Name: "showbridge",
|
Name: "showbridge",
|
||||||
|
Usage: "Simple protocol router /s",
|
||||||
Version: version,
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -5,12 +5,12 @@ 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.18
|
gitlab.com/gomidi/midi/v2 v2.3.18
|
||||||
go.bug.st/serial v1.6.4
|
go.bug.st/serial v1.6.4
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -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,8 +44,8 @@ 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=
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package module
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ type MIDIInput struct {
|
|||||||
router route.RouteIO
|
router route.RouteIO
|
||||||
Port string
|
Port string
|
||||||
SendFunc func(midi.Message) error
|
SendFunc func(midi.Message) error
|
||||||
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -29,39 +31,39 @@ func init() {
|
|||||||
port, ok := params["port"]
|
port, ok := params["port"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.input requires a port parameter")
|
return nil, errors.New("midi.input requires a port parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
portString, ok := port.(string)
|
portString, ok := port.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.input port must be a string")
|
return nil, errors.New("midi.input port must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router}, nil
|
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIInput) Id() string {
|
func (mi *MIDIInput) Id() string {
|
||||||
return mc.config.Id
|
return mi.config.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIInput) Type() string {
|
func (mi *MIDIInput) Type() string {
|
||||||
return mc.config.Type
|
return mi.config.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIInput) Run() error {
|
func (mi *MIDIInput) Run() error {
|
||||||
defer midi.CloseDriver()
|
defer midi.CloseDriver()
|
||||||
|
|
||||||
in, err := midi.FindInPort(mc.Port)
|
in, err := midi.FindInPort(mi.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("midi.input can't find input port: %s", mc.Port)
|
return fmt.Errorf("midi.input can't find input port: %s", mi.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
|
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
|
||||||
if mc.router != nil {
|
if mi.router != nil {
|
||||||
mc.router.HandleInput(mc.Id(), msg)
|
mi.router.HandleInput(mi.Id(), msg)
|
||||||
}
|
}
|
||||||
}, midi.UseSysEx())
|
}, midi.UseSysEx())
|
||||||
|
|
||||||
@@ -71,11 +73,11 @@ func (mc *MIDIInput) Run() error {
|
|||||||
|
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
<-mc.ctx.Done()
|
<-mi.ctx.Done()
|
||||||
slog.Debug("router context done in module", "id", mc.Id())
|
mi.logger.Debug("router context done in module")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIInput) Output(payload any) error {
|
func (mi *MIDIInput) Output(payload any) error {
|
||||||
return fmt.Errorf("midi.input output is not implemented")
|
return errors.New("midi.input output is not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package module
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
@@ -19,11 +20,11 @@ type MIDIOutput struct {
|
|||||||
router route.RouteIO
|
router route.RouteIO
|
||||||
Port string
|
Port string
|
||||||
SendFunc func(midi.Message) error
|
SendFunc func(midi.Message) error
|
||||||
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterModule(ModuleRegistration{
|
RegisterModule(ModuleRegistration{
|
||||||
//TODO(jwetzell): find a better namespace than "misc"
|
|
||||||
Type: "midi.output",
|
Type: "midi.output",
|
||||||
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
|
||||||
@@ -31,35 +32,35 @@ func init() {
|
|||||||
port, ok := params["port"]
|
port, ok := params["port"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.output requires a port parameter")
|
return nil, errors.New("midi.output requires a port parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
portString, ok := port.(string)
|
portString, ok := port.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.output port must be a string")
|
return nil, errors.New("midi.output port must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router}, nil
|
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIOutput) Id() string {
|
func (mo *MIDIOutput) Id() string {
|
||||||
return mc.config.Id
|
return mo.config.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIOutput) Type() string {
|
func (mo *MIDIOutput) Type() string {
|
||||||
return mc.config.Type
|
return mo.config.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIOutput) Run() error {
|
func (mo *MIDIOutput) Run() error {
|
||||||
defer midi.CloseDriver()
|
defer midi.CloseDriver()
|
||||||
|
|
||||||
out, err := midi.FindOutPort(mc.Port)
|
out, err := midi.FindOutPort(mo.Port)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("midi.output can't find output port: %s", mc.Port)
|
return fmt.Errorf("midi.output can't find output port: %s", mo.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
send, err := midi.SendTo(out)
|
send, err := midi.SendTo(out)
|
||||||
@@ -67,23 +68,23 @@ func (mc *MIDIOutput) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.SendFunc = send
|
mo.SendFunc = send
|
||||||
|
|
||||||
<-mc.ctx.Done()
|
<-mo.ctx.Done()
|
||||||
slog.Debug("router context done in module", "id", mc.Id())
|
mo.logger.Debug("router context done in module")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MIDIOutput) Output(payload any) error {
|
func (mo *MIDIOutput) Output(payload any) error {
|
||||||
if mc.SendFunc == nil {
|
if mo.SendFunc == nil {
|
||||||
return fmt.Errorf("midi.output output is not setup")
|
return errors.New("midi.output output is not setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadMessage, ok := payload.(midi.Message)
|
payloadMessage, ok := payload.(midi.Message)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("midi.output can only ouptut midi.Message")
|
return errors.New("midi.output can only ouptut midi.Message")
|
||||||
}
|
}
|
||||||
|
|
||||||
return mc.SendFunc(payloadMessage)
|
return mo.SendFunc(payloadMessage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package processor
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"gitlab.com/gomidi/midi/v2"
|
"gitlab.com/gomidi/midi/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(jwetzell): support using numbers in config file treated as hardcoded values
|
||||||
type MIDIMessageCreate struct {
|
type MIDIMessageCreate struct {
|
||||||
config config.ProcessorConfig
|
config config.ProcessorConfig
|
||||||
ProcessFunc func(ctx context.Context, payload any) (any, error)
|
ProcessFunc func(ctx context.Context, payload any) (any, error)
|
||||||
@@ -33,13 +35,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
|
|||||||
channel, ok := params["channel"]
|
channel, ok := params["channel"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
|
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelString, ok := channel.(string)
|
channelString, ok := channel.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
|
return nil, errors.New("midi.message.create NoteOn channel must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelTemplate, err := template.New("channel").Parse(channelString)
|
channelTemplate, err := template.New("channel").Parse(channelString)
|
||||||
@@ -51,13 +53,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
|
|||||||
note, ok := params["note"]
|
note, ok := params["note"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
|
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
noteString, ok := note.(string)
|
noteString, ok := note.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
|
return nil, errors.New("midi.message.create NoteOn note must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
noteTemplate, err := template.New("note").Parse(noteString)
|
noteTemplate, err := template.New("note").Parse(noteString)
|
||||||
@@ -69,13 +71,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
|
|||||||
velocity, ok := params["velocity"]
|
velocity, ok := params["velocity"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn requires a velocity parameter")
|
return nil, errors.New("midi.message.create NoteOn requires a velocity parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
velocityString, ok := velocity.(string)
|
velocityString, ok := velocity.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn velocity must be a string")
|
return nil, errors.New("midi.message.create NoteOn velocity must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
velocityTemplate, err := template.New("velocity").Parse(velocityString)
|
velocityTemplate, err := template.New("velocity").Parse(velocityString)
|
||||||
@@ -124,13 +126,13 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
|
|||||||
channel, ok := params["channel"]
|
channel, ok := params["channel"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
|
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelString, ok := channel.(string)
|
channelString, ok := channel.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
|
return nil, errors.New("midi.message.create NoteOn channel must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelTemplate, err := template.New("channel").Parse(channelString)
|
channelTemplate, err := template.New("channel").Parse(channelString)
|
||||||
@@ -142,13 +144,13 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
|
|||||||
note, ok := params["note"]
|
note, ok := params["note"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
|
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
noteString, ok := note.(string)
|
noteString, ok := note.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
|
return nil, errors.New("midi.message.create NoteOn note must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
noteTemplate, err := template.New("note").Parse(noteString)
|
noteTemplate, err := template.New("note").Parse(noteString)
|
||||||
@@ -189,13 +191,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
|
|||||||
channel, ok := params["channel"]
|
channel, ok := params["channel"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange requires a channel parameter")
|
return nil, errors.New("midi.message.create ControlChange requires a channel parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelString, ok := channel.(string)
|
channelString, ok := channel.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange channel must be a string")
|
return nil, errors.New("midi.message.create ControlChange channel must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelTemplate, err := template.New("channel").Parse(channelString)
|
channelTemplate, err := template.New("channel").Parse(channelString)
|
||||||
@@ -207,13 +209,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
|
|||||||
controller, ok := params["controller"]
|
controller, ok := params["controller"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange requires a controller parameter")
|
return nil, errors.New("midi.message.create ControlChange requires a controller parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerString, ok := controller.(string)
|
controllerString, ok := controller.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange controller must be a string")
|
return nil, errors.New("midi.message.create ControlChange controller must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerTemplate, err := template.New("controller").Parse(controllerString)
|
controllerTemplate, err := template.New("controller").Parse(controllerString)
|
||||||
@@ -225,13 +227,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
|
|||||||
value, ok := params["value"]
|
value, ok := params["value"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange requires a value parameter")
|
return nil, errors.New("midi.message.create ControlChange requires a value parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
valueString, ok := value.(string)
|
valueString, ok := value.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ControlChange value must be a string")
|
return nil, errors.New("midi.message.create ControlChange value must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTemplate, err := template.New("value").Parse(valueString)
|
valueTemplate, err := template.New("value").Parse(valueString)
|
||||||
@@ -281,13 +283,13 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
|
|||||||
channel, ok := params["channel"]
|
channel, ok := params["channel"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ProgramChange requires a channel parameter")
|
return nil, errors.New("midi.message.create ProgramChange requires a channel parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelString, ok := channel.(string)
|
channelString, ok := channel.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ProgramChange channel must be a string")
|
return nil, errors.New("midi.message.create ProgramChange channel must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
channelTemplate, err := template.New("channel").Parse(channelString)
|
channelTemplate, err := template.New("channel").Parse(channelString)
|
||||||
@@ -299,13 +301,13 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
|
|||||||
program, ok := params["program"]
|
program, ok := params["program"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ProgramChange requires a program parameter")
|
return nil, errors.New("midi.message.create ProgramChange requires a program parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
programString, ok := program.(string)
|
programString, ok := program.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create ProgramChange program must be a string")
|
return nil, errors.New("midi.message.create ProgramChange program must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
programTemplate, err := template.New("program").Parse(programString)
|
programTemplate, err := template.New("program").Parse(programString)
|
||||||
@@ -348,13 +350,13 @@ func init() {
|
|||||||
msgType, ok := params["type"]
|
msgType, ok := params["type"]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create requires a type parameter")
|
return nil, errors.New("midi.message.create requires a type parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
msgTypeString, ok := msgType.(string)
|
msgTypeString, ok := msgType.(string)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("midi.message.create type parameter must be a string")
|
return nil, errors.New("midi.message.create type parameter must be a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msgTypeString {
|
switch msgTypeString {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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, "?", ".")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
58
internal/processor/script-expr_test.go
Normal file
58
internal/processor/script-expr_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
48
internal/processor/script-js_test.go
Normal file
48
internal/processor/script-js_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
25
router.go
25
router.go
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user