Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions cmd/modbus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0

// Package main contains modbus-adapter main function to start the modbus-adapter service.
package main

import (
"context"
"fmt"
"log"
"os"

chclient "github.com/mainflux/callhome/pkg/client"
"github.com/mainflux/mainflux"
jaegerClient "github.com/mainflux/mainflux/internal/clients/jaeger"
"github.com/mainflux/mainflux/internal/env"
"github.com/mainflux/mainflux/internal/server"
mflog "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/modbus"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/messaging/brokers"
brokerstracing "github.com/mainflux/mainflux/pkg/messaging/brokers/tracing"
"github.com/mainflux/mainflux/pkg/uuid"
"golang.org/x/sync/errgroup"
)

const (
svcName = "modbus"
envPrefixES = "MF_MODBUS_ADAPTER_"
)

type config struct {
LogLevel string `env:"MF_MODBUS_ADAPTER_LOG_LEVEL" envDefault:"info"`
JaegerURL string `env:"MF_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
BrokerURL string `env:"MF_BROKER_URL" envDefault:"nats://localhost:4222"`
SendTelemetry bool `env:"MF_SEND_TELEMETRY" envDefault:"true"`
InstanceID string `env:"MF_MODBUS_ADAPTER_INSTANCE_ID" envDefault:""`
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)

cfg := config{}
if err := env.Parse(&cfg); err != nil {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}

logger, err := mflog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("failed to init logger: %s", err)
}

if cfg.InstanceID == "" {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Fatal(fmt.Sprintf("failed to generate instanceID: %s", err))
}
}

tp, err := jaegerClient.NewProvider(svcName, cfg.JaegerURL, cfg.InstanceID)
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to init Jaeger: %s", err))
}
var exitCode int
defer mflog.ExitWithError(&exitCode)

defer func() {
if err := tp.Shutdown(ctx); err != nil {
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
}
}()
tracer := tp.Tracer(svcName)

nps, err := brokers.NewPubSub(cfg.BrokerURL, "", logger)
if err != nil {
logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err))
exitCode = 1
return
}
defer nps.Close()
nps = brokerstracing.NewPubSub(server.Config{}, tracer, nps)

svc := modbus.New(logger)

if err := svc.Read(ctx, svcName, nps, nps); err != nil {
logger.Error(fmt.Sprintf("failed to forward read messages: %v", err))
exitCode = 1
return
}

if err := svc.Write(ctx, svcName, nps, nps); err != nil {
logger.Error(fmt.Sprintf("failed to forward write messages: %v", err))
exitCode = 1
return
}

if cfg.SendTelemetry {
chc := chclient.New(svcName, mainflux.Version, logger, cancel)
go chc.CallHome(ctx)
}

g.Go(func() error {
if sig := errors.SignalHandler(ctx); sig != nil {
cancel()
logger.Info(fmt.Sprintf("modbus shutdown by signal: %s", sig))
}
return nil
})

if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("modbus service terminated: %s", err))
}
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)

require (
github.com/goburrow/serial v0.1.0
github.com/tbrandon/mbserver v0.0.0-20211210035124-daf3c8c4269f
)

require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
Expand Down Expand Up @@ -95,6 +100,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.1 // indirect
github.com/goburrow/modbus v0.1.0
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ github.com/go-zoo/bone v1.3.0/go.mod h1:HI3Lhb7G3UQcAwEhOJ2WyNcsFtQX1WYHa0Hl4OBb
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY=
github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro=
github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg=
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v1.5.2 h1:WnKf8xRQImcT/KLaEWG2pjEeryDB7K0qQN9mPs1C58Q=
Expand Down Expand Up @@ -611,6 +615,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tbrandon/mbserver v0.0.0-20211210035124-daf3c8c4269f h1:RSsPHbWJpo/IaGb+7S7hNIQtuLfli2kIi97clK7BW/o=
github.com/tbrandon/mbserver v0.0.0-20211210035124-daf3c8c4269f/go.mod h1:qUzPVlSj2UgxJkVbH0ZwuuiR46U8RBMDT5KLY78Ifpw=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
Expand Down
135 changes: 135 additions & 0 deletions modbus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Mainflux Modbus Adapter

The Mainflux Modbus Adapter service is responsible for reading and writing data to Modbus sensors using various protocols such as TCP and RTU/ASCII. It serves as an interface between Mainflux and Modbus devices, allowing you to easily integrate Modbus devices into your IoT ecosystem.

## Configuration

The service is configured using the environment variables presented in the
following table. Note that any unset variables will be replaced with their
default values.

| Variable | Description | Default |
| -------------------------------- | --------------------------------------------------- | ------------------------------ |
| MF_MODBUS_ADAPTER_LOG_LEVEL | Service log level | info |
| MF_BROKER_URL | Message broker instance URL | nats://localhost:4222 |
| MF_JAEGER_URL | Jaeger server URL | http://jaeger:14268/api/traces |
| MF_SEND_TELEMETRY | Send telemetry to mainflux call home server | true |
| MF_MODBUS_ADAPTER_INSTANCE_ID | Modbus adapter instance ID | |

## Deployment

The service itself is distributed as Docker container. Check the [`modbus-adapter`](https://github.com/mainflux/mainflux/blob/master/docker/docker-compose.yml#L273-L291) service section in
docker-compose to see how service is deployed.

Running this service outside of container requires working instance of the message broker service.
To start the service outside of the container, execute the following shell script:

```bash
# download the latest version of the service
git clone https://github.com/mainflux/mainflux

cd mainflux

# compile the http
make modbus

# copy binary to bin
make install

# set the environment variables and run the service
MF_MODBUS_ADAPTER_LOG_LEVEL=[Service log level] \
MF_BROKER_URL=[Message broker instance URL] \
MF_JAEGER_URL=[Jaeger server URL] \
MF_SEND_TELEMETRY=[Send telemetry to mainflux call home server] \
MF_MODBUS_ADAPTER_INSTANCE_ID=[CoAP adapter instance ID] \
$GOBIN/mainflux-modbus
```

## Usage

The Mainflux Modbus Adapter service interacts with Modbus sensors by subscribing to specific channels for reading and writing Modbus values. It utilizes the Mainflux messaging system and follows a specific payload structure for configuration.

### Reading Values

To start reading values, you need to publish a message using mainflux messaging adapters such as http, coap, mqtt etc to the channel `channels/<channel_id>/messages/modbus/read/<modbus_protocol>/<modbus_data_point>`.

The supported modbus protocols include:

- TCP
- RTU

The supported data points include:

- coil
- h_register
- i_register
- register
- discrete
- fifo

The payload of the message is structured as follows:

```json
{
"options": {
"address": 123,
"quantity": 2,
},
"config": {
"sampling_frquency: ""
...
}
}

```

The config can be eithee RTU or TCP and has the following structures respectively:

```json
{
"address": "/dev/ttyS0",
"baud_rate": 9600,
"config": {},
"data_bits": 8,
"idle_timeout": "5m",
"parity": "even",
"rs485": {},
"slave_id": 1,
"stop_bits": 1,
"timeout": "10s",
"sampling_frequency": "1s"
}
```

```json
{
"address": "localhost:1502",
"idle_time": "15m",
"slave_id": 2,
"timeout": "5s",
"sampling_frequency": "30s"
}
```

The results of the readings are published on `channels/<channel_id>/messages/modbus/res`

### Writing Values

To start writing values, you need to publish a message using mainflux messaging adapters such as http, coap, mqtt etc to the channel `channels/<channel_id>/messages/modbus/write/<modbus_protocol>/<modbus_data_point>`.

The payload of the message is structured as follows:

```json
{
"options": {
"address": 123,
"quantity": 2,
"value": {}
},
"config": {}
}
```

The value field can be either `uint16` or `[]byte`.

The results of the readings are published on `channels/<channel_id>/messages/modbus/res`
Loading