Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UV-278 - Modbus Adapter #1

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
40 changes: 40 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
The GitHub issue tracker is for bug reports and feature requests. General support can be found at
the following locations:

- Google group - https://groups.google.com/forum/#!forum/mainflux
- Gitter - https://gitter.im/mainflux/mainflux
-->

**FEATURE REQUEST**

1. Is there an open issue addressing this request? If it does, please add a "+1" reaction to the
existing issue, otherwise proceed to step 2.

2. Describe the feature you are requesting, as well as the possible use case(s) for it.

3. Indicate the importance of this feature to you (must-have, should-have, nice-to-have).

**BUG REPORT**

1. What were you trying to achieve?

2. What are the expected results?

3. What are the received results?

4. What are the steps to reproduce the issue?

5. In what environment did you encounter the issue?

6. Additional information you deem important:

**ENHANCEMENT**
1. Describe the enhancement you are requesting. Enhancements include:
- tests
- code refactoring
- documentation
- research
- tooling

2. Indicate the importance of this enhancement to you (must-have, should-have, nice-to-have).
15 changes: 15 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Pull request title should be `MF-XXX - description` or `NOISSUE - description` where XXX is ID of issue that this PR relate to.
Please review the [CONTRIBUTING.md](https://github.com/mainflux/mainflux/blob/master/CONTRIBUTING.md) file for detailed contributing guidelines.

### What does this do?

### Which issue(s) does this PR fix/relate to?
Put here `Resolves #XXX` to auto-close the issue that your PR fixes (if such)

### List any changes that modify/break current functionality

### Have you included tests for your changes?

### Did you document any new/modified functionality?

### Notes
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
strategy:
matrix:
go-version: [1.21.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v3
- name: Build
run: go build -v ./...
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --no-config --disable-all --enable gosimple --enable errcheck --enable govet --enable unused --enable goconst --enable godot --enable unused --enable deadcode --timeout 3m
- name: Run tests
run: go test -mod=vendor -v --race -covermode=atomic -coverprofile cover.out ./...
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0

# Set your private global .gitignore:
# https://digitalfortress.tech/tricks/creating-a-global-gitignore/

build

121 changes: 121 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0

MF_DOCKER_IMAGE_NAME_PREFIX ?= mainflux
BUILD_DIR = build
SERVICES = modbus
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOARCH ?= amd64
VERSION ?= $(shell git describe --abbrev=0 --tags)
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)

ifneq ($(MF_BROKER_TYPE),)
MF_BROKER_TYPE := $(MF_BROKER_TYPE)
else
MF_BROKER_TYPE=nats
endif

define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
go build -tags $(MF_BROKER_TYPE) -ldflags "-s -w \
-X 'github.com/mainflux/mainflux.BuildTime=$(TIME)' \
-X 'github.com/mainflux/mainflux.Version=$(VERSION)' \
-X 'github.com/mainflux/mainflux.Commit=$(COMMIT)'" \
-o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
endef

define make_docker
$(eval svc=$(subst docker_,,$(1)))

docker build \
--no-cache \
--build-arg SVC=$(svc) \
--build-arg GOARCH=$(GOARCH) \
--build-arg GOARM=$(GOARM) \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg TIME=$(TIME) \
--tag=$(MF_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \
-f docker/Dockerfile .
endef

define make_docker_dev
$(eval svc=$(subst docker_dev_,,$(1)))

docker build \
--no-cache \
--build-arg SVC=$(svc) \
--tag=$(MF_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \
-f docker/Dockerfile.dev ./build
endef

all: $(SERVICES)

.PHONY: all $(SERVICES) dockers dockers_dev latest release

clean:
rm -rf ${BUILD_DIR}

cleandocker:
# Stops containers and removes containers, networks, volumes, and images created by up
docker-compose -f docker/docker-compose.yml down --rmi all -v --remove-orphans

ifdef pv
# Remove unused volumes
docker volume ls -f name=$(MF_DOCKER_IMAGE_NAME_PREFIX) -f dangling=true -q | xargs -r docker volume rm
endif

install:
cp ${BUILD_DIR}/* $(GOBIN)

test:
go test -mod=vendor -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')

proto:
SammyOina marked this conversation as resolved.
Show resolved Hide resolved
protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative users/policies/*.proto
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative things/policies/*.proto

$(SERVICES):
$(call compile_service,$(@))

$(DOCKERS):
$(call make_docker,$(@),$(GOARCH))

$(DOCKERS_DEV):
$(call make_docker_dev,$(@))

dockers: $(DOCKERS)
dockers_dev: $(DOCKERS_DEV)

define docker_push
for svc in $(SERVICES); do \
docker push $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(1); \
done
endef

changelog:
git log $(shell git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s"

latest: dockers
$(call docker_push,latest)

release:
$(eval version = $(shell git describe --abbrev=0 --tags))
git checkout $(version)
$(MAKE) dockers
for svc in $(SERVICES); do \
docker tag $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(version); \
done
$(call docker_push,$(version))

rundev:
SammyOina marked this conversation as resolved.
Show resolved Hide resolved
cd scripts && ./run.sh

run:
sed -i "s,file: brokers/.*.yml,file: brokers/${MF_BROKER_TYPE}.yml," docker/docker-compose.yml
sed -i "s,MF_BROKER_URL=.*,MF_BROKER_URL=$$\{MF_$(shell echo ${MF_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env
docker compose -f docker/docker-compose.yml up
122 changes: 122 additions & 0 deletions cmd/modbus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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"

"github.com/caarlos0/env/v7"
jaegerClient "github.com/mainflux/edge/internal/clients/jaeger"
"github.com/mainflux/edge/internal/server"
"github.com/mainflux/edge/internal/server/http"
"github.com/mainflux/edge/modbus"
"github.com/mainflux/edge/modbus/api"
mflog "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/messaging/brokers"
"github.com/mainflux/mainflux/pkg/uuid"
"golang.org/x/sync/errgroup"
)

const (
svcName = "modbus"
envPrefix = "MF_MODBUS_ADAPTER_"
defSvcHTTPPort = "9990"
)

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"`
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))
}
}()

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

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
}

httpServerConfig := server.Config{Port: defSvcHTTPPort}
if err := env.Parse(&httpServerConfig, env.Options{Prefix: envPrefix}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
exitCode = 1
return
}

hs := http.New(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(nps, cfg.InstanceID), logger)

g.Go(func() error {
return hs.Start()
})

g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs)
})

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))
}
}
24 changes: 24 additions & 0 deletions docker/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Nats
MF_NATS_PORT=4222
MF_NATS_URL=nats://broker:${MF_NATS_PORT}

## RabbitMQ
MF_RABBITMQ_PORT=5672
MF_RABBITMQ_HTTP_PORT=15672
MF_RABBITMQ_USER=mainflux
MF_RABBITMQ_PASS=mainflux
MF_RABBITMQ_COOKIE=mainflux
MF_RABBITMQ_VHOST=/
MF_RABBITMQ_URL=amqp://${MF_RABBITMQ_USER}:${MF_RABBITMQ_PASS}@broker:${MF_RABBITMQ_PORT}${MF_RABBITMQ_VHOST}

## Message Broker
MF_BROKER_TYPE=nats
MF_BROKER_URL=${MF_NATS_URL}

### Modbus
MF_MODBUS_ADAPTER_LOG_LEVEL=info
MF_MODBUS_ADAPTER_INSTANCE_ID=
MF_MODBUS_ADAPTER_HTTP_HOST=modbus
MF_MODBUS_ADAPTER_HTTP_PORT=9990
MF_MODBUS_ADAPTER_HTTP_SERVER_CERT=
MF_MODBUS_ADAPTER_HTTP_SERVER_KEY=
8 changes: 8 additions & 0 deletions docker/brokers/nats.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
broker:
image: nats:2.2.4-alpine
command: "-c /etc/nats/nats.conf -DV"
volumes:
- ./../nats/:/etc/nats
ports:
- ${MF_NATS_PORT}:${MF_NATS_PORT}
11 changes: 11 additions & 0 deletions docker/brokers/rabbitmq.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
broker:
image: rabbitmq:3.9.20-management-alpine
environment:
RABBITMQ_ERLANG_COOKIE: ${MF_RABBITMQ_COOKIE}
RABBITMQ_DEFAULT_USER: ${MF_RABBITMQ_USER}
RABBITMQ_DEFAULT_PASS: ${MF_RABBITMQ_PASS}
RABBITMQ_DEFAULT_VHOST: ${MF_RABBITMQ_VHOST}
ports:
- ${MF_RABBITMQ_PORT}:${MF_RABBITMQ_PORT}
- ${MF_RABBITMQ_HTTP_PORT}:${MF_RABBITMQ_HTTP_PORT}
Loading