Skip to content

Commit c48247e

Browse files
authored
MQTT producer app and IPC context cancellation (#54)
1 parent 8ba934a commit c48247e

16 files changed

Lines changed: 242 additions & 72 deletions

File tree

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ $ docker run --name gsw-service \
3838

3939
### `docker-compose`
4040

41-
For simplicity, a `docker-compose` file is provided for building and running GSW, Grafana, and InfluxDB:
41+
For simplicity, a `docker-compose` file is provided for building and running GSW, Mosquitto, Grafana, and InfluxDB:
4242
```shell
4343
$ docker compose up --build
4444
```
4545

46+
If you choose to run GSW on the host, but want to run the services in containers, you could run only the services using:
47+
```shell
48+
$ docker compose -f compose-services.yaml up --build
49+
```
50+
4651
#### Accessing Grafana
4752

4853
Grafana should be exposed at <http://localhost:3000>.

cmd/Containerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RUN --mount=type=cache,id=gsw_mod,target=/go/pkg/mod \
2222

2323
RUN --mount=type=cache,id=gsw_mod,target=/go/pkg/mod \
2424
--mount=type=cache,id=gsw_build,target=/root/.cache/go-build \
25-
for binary in grafana_live live_setup mem_view ipc_benchmark telem_view udp_command; do \
25+
for binary in grafana_live live_setup mem_view ipc_benchmark telem_view udp_command mqtt_producer; do \
2626
CGO_ENABLED=1 GOOS=linux go build \
2727
-trimpath \
2828
-o /build-output/bin/$binary \

cmd/grafana_live/grafana_live.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"context"
56
"flag"
67
"fmt"
78
"io"
@@ -44,7 +45,7 @@ func streamTelemetryPacket(packet tlm.TelemetryPacket, config *viper.Viper, auth
4445
// stream data over WebSocket
4546
if websocketConn != nil {
4647
for {
47-
p, err := reader.Read()
48+
p, err := reader.Read(context.TODO())
4849
if err != nil {
4950
fmt.Printf("Error reading packet: %v\n", err)
5051
continue
@@ -70,7 +71,7 @@ func streamTelemetryPacket(packet tlm.TelemetryPacket, config *viper.Viper, auth
7071
if config.GetBool("use_http") {
7172
liveAddr := config.GetString("http_addr")
7273
for {
73-
p, err := reader.Read()
74+
p, err := reader.Read(context.TODO())
7475
if err != nil {
7576
fmt.Printf("Error reading packet: %v\n", err)
7677
continue

cmd/ipc_benchmark/reader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func packetReader(ctx context.Context, packet tlm.TelemetryPacket) *OutputPacket
7171
if err := ctx.Err(); err != nil {
7272
break
7373
}
74-
p, err := reader.Read()
74+
p, err := reader.Read(ctx)
7575
if err != nil {
7676
log.Fatal(fmt.Errorf("couldn't read packet: %w", err))
7777
}

cmd/mem_view/mem_view.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
67
"os"
@@ -56,7 +57,7 @@ func printTelemetryPacket(startLine int, packet tlm.TelemetryPacket) {
5657
fmt.Print(buildString(packet, make([]byte, proc.GetPacketSize(packet)), startLine))
5758

5859
for {
59-
p, err := reader.Read()
60+
p, err := reader.Read(context.TODO())
6061
if err != nil {
6162
fmt.Printf("Error reading packet: %v\n", err)
6263
continue

cmd/mqtt_producer/main.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// NOTE(mia): Currently this is a minimal best-effort bridge that will not
2+
// scale with higher message rates, yet. Measurements that either the MQTT
3+
// broker or this bridge can't process will be dropped.
4+
// refer to https://github.com/RIT-Launch-Initiative/GSW/issues/59
5+
6+
package main
7+
8+
import (
9+
"context"
10+
"encoding/json"
11+
"errors"
12+
"flag"
13+
"fmt"
14+
"log"
15+
"os"
16+
"os/signal"
17+
"sync"
18+
"syscall"
19+
20+
"github.com/AarC10/GSW-V2/lib/tlm"
21+
"github.com/AarC10/GSW-V2/proc"
22+
mqtt "github.com/eclipse/paho.mqtt.golang"
23+
)
24+
25+
var (
26+
shmDir = flag.String("shm", "/dev/shm", "directory to use for shared memory")
27+
brokerURL = flag.String("broker", "tcp://127.0.0.1:1883", "mqtt broker url")
28+
topicPrefix = flag.String("topic_prefix", "gsw", "mqtt topic prefix")
29+
)
30+
31+
func main() {
32+
flag.Parse()
33+
34+
configData, err := proc.ReadTelemetryConfigFromShm(*shmDir)
35+
if err != nil {
36+
log.Fatal(err)
37+
}
38+
_, err = proc.ParseConfigBytes(configData)
39+
if err != nil {
40+
log.Fatal(err)
41+
}
42+
43+
opts := mqtt.NewClientOptions()
44+
opts.AddBroker(*brokerURL)
45+
opts.SetClientID("gsw-mqtt-app")
46+
opts.SetCleanSession(true)
47+
48+
client := mqtt.NewClient(opts)
49+
if token := client.Connect(); token.Wait() && token.Error() != nil {
50+
log.Fatal(token.Error())
51+
}
52+
53+
ctx, cancel := context.WithCancel(context.Background())
54+
55+
var wg sync.WaitGroup
56+
57+
for _, packet := range proc.GswConfig.TelemetryPackets {
58+
wg.Add(1)
59+
go func(packet tlm.TelemetryPacket) {
60+
defer wg.Done()
61+
err := packetWriter(ctx, packet, client)
62+
if err != nil && !errors.Is(err, context.Canceled) {
63+
log.Printf("error in writer: %v", err)
64+
}
65+
}(packet)
66+
}
67+
68+
go func() {
69+
sigChan := make(chan os.Signal, 1)
70+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
71+
<-sigChan
72+
log.Println("shutting down")
73+
cancel()
74+
}()
75+
76+
wg.Wait()
77+
client.Disconnect(250)
78+
}
79+
80+
func packetWriter(ctx context.Context, packet tlm.TelemetryPacket, client mqtt.Client) error {
81+
pLog := log.New(os.Stderr, fmt.Sprintf("[%s] ", packet.Name), log.LstdFlags|log.Lmsgprefix)
82+
pLog.Println("starting streaming")
83+
84+
reader, err := proc.NewIpcShmReaderForPacket(packet, *shmDir)
85+
if err != nil {
86+
return fmt.Errorf("couldn't create reader: %w", err)
87+
}
88+
defer reader.Cleanup()
89+
90+
for {
91+
p, err := reader.Read(ctx)
92+
if ctx.Err() != nil {
93+
return ctx.Err()
94+
}
95+
if err != nil {
96+
pLog.Printf("error reading packet: %v\n", err)
97+
continue
98+
}
99+
data := p.Data()
100+
offset := 0
101+
for _, name := range packet.Measurements {
102+
meas, ok := proc.GswConfig.Measurements[name]
103+
if !ok || offset+meas.Size > len(data) {
104+
continue
105+
}
106+
val, err := tlm.InterpretMeasurementValue(meas, data[offset:offset+meas.Size])
107+
if err != nil {
108+
pLog.Printf("error interpreting measurement: %v\n", err)
109+
continue
110+
}
111+
jsonStr, err := json.Marshal(val)
112+
if err != nil {
113+
pLog.Printf("error marshaling measurement: %v\n", err)
114+
continue
115+
}
116+
// qos=0 delivery not guaranteed
117+
client.Publish(fmt.Sprintf("%s/%s/%s", *topicPrefix, packet.Name, name), 0, false, jsonStr)
118+
offset += meas.Size
119+
}
120+
}
121+
}

cmd/telem_view/telem_view.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
67
"os"
@@ -151,7 +152,7 @@ func main() {
151152
defer reader.Cleanup()
152153

153154
for {
154-
p, err := reader.Read()
155+
p, err := reader.Read(context.TODO())
155156
if err != nil {
156157
fmt.Printf("Error reading packet: %v\n", err)
157158
continue

compose-services.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
services:
2+
grafana:
3+
build:
4+
context: data/grafana
5+
dockerfile: Containerfile
6+
ports:
7+
- "127.0.0.1:3000:3000/tcp"
8+
depends_on:
9+
influxdb:
10+
condition: service_started
11+
environment:
12+
- GSW_GRAFANA_INFLUXDB_URL=http://influxdb:8086
13+
- GSW_GRAFANA_INFLUXDB_USER=root
14+
- GSW_GRAFANA_INFLUXDB_PASSWORD=root
15+
- GF_AUTH_ANONYMOUS_ENABLED=true
16+
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
17+
- GF_AUTH_BASIC_ENABLED=false
18+
volumes:
19+
- grafana-data:/var/lib/grafana
20+
influxdb:
21+
image: influxdb:1.11-alpine
22+
volumes:
23+
- influxdb-data:/var/lib/influxdb
24+
environment:
25+
- INFLUXDB_UDP_ENABLED=true
26+
- INFLUXDB_UDP_DATABASE=gsw
27+
ports:
28+
- "127.0.0.1:8089:8089/udp"
29+
30+
mosquitto:
31+
build:
32+
context: data/mosquitto
33+
dockerfile: Containerfile
34+
ports:
35+
- "127.0.0.1:1883:1883/tcp"
36+
- "127.0.0.1:1880:1880/tcp"
37+
38+
volumes:
39+
grafana-data:
40+
influxdb-data:

compose.yaml

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
include:
2+
- compose-services.yaml
3+
14
services:
25
gsw:
36
build:
@@ -17,33 +20,3 @@ services:
1720
- 12005:12005/udp
1821
- 12006:12006/udp
1922
- 12002:12002/udp
20-
21-
grafana:
22-
build:
23-
context: data/grafana
24-
dockerfile: Containerfile
25-
ports:
26-
- "127.0.0.1:3000:3000/tcp"
27-
depends_on:
28-
influxdb:
29-
condition: service_started
30-
environment:
31-
- GSW_GRAFANA_INFLUXDB_URL=http://influxdb:8086
32-
- GSW_GRAFANA_INFLUXDB_USER=root
33-
- GSW_GRAFANA_INFLUXDB_PASSWORD=root
34-
- GF_AUTH_ANONYMOUS_ENABLED=true
35-
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
36-
- GF_AUTH_BASIC_ENABLED=false
37-
volumes:
38-
- grafana-data:/var/lib/grafana
39-
influxdb:
40-
image: influxdb:1.11-alpine
41-
volumes:
42-
- influxdb-data:/var/lib/influxdb
43-
environment:
44-
- INFLUXDB_UDP_ENABLED=true
45-
- INFLUXDB_UDP_DATABASE=gsw
46-
47-
volumes:
48-
grafana-data:
49-
influxdb-data:

data/mosquitto/Containerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM eclipse-mosquitto:2
2+
3+
COPY mosquitto.conf /mosquitto/config/mosquitto.conf

0 commit comments

Comments
 (0)