initial
This commit is contained in:
commit
690f0f3cf5
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
ENV
|
38
.gitlab-ci.yml
Normal file
38
.gitlab-ci.yml
Normal file
@ -0,0 +1,38 @@
|
||||
stages:
|
||||
- check
|
||||
- build
|
||||
|
||||
variables:
|
||||
IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH
|
||||
|
||||
check:
|
||||
image: registry.hottis.de/dockerized/base-build-env:latest
|
||||
stage: check
|
||||
tags:
|
||||
- hottis
|
||||
- linux
|
||||
- docker
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- checksemver.py -v
|
||||
--versionToValidate "${CI_COMMIT_TAG}"
|
||||
--validateMessage
|
||||
--messageToValidate "${CI_COMMIT_MESSAGE}"
|
||||
|
||||
build:
|
||||
image: registry.hottis.de/dockerized/docker-bash:latest
|
||||
stage: build
|
||||
tags:
|
||||
- hottis
|
||||
- linux
|
||||
- docker
|
||||
script:
|
||||
- docker build --tag $IMAGE_NAME:latest .
|
||||
- if [ "$CI_COMMIT_TAG" != "" ]; then
|
||||
docker tag $IMAGE_NAME:latest $IMAGE_NAME:${CI_COMMIT_TAG};
|
||||
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY;
|
||||
docker push $IMAGE_NAME:latest;
|
||||
docker push $IMAGE_NAME:${CI_COMMIT_TAG};
|
||||
fi
|
||||
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@ -0,0 +1,41 @@
|
||||
FROM python:latest
|
||||
|
||||
LABEL Maintainer="Wolfgang Hottgenroth wolfgang.hottgenroth@icloud.com"
|
||||
LABEL ImageName="registry.hottis.de/wolutator/modbusservice"
|
||||
|
||||
ARG APP_DIR="/opt/app"
|
||||
ARG CONF_DIR="${APP_DIR}/config"
|
||||
|
||||
|
||||
|
||||
RUN \
|
||||
apt update && \
|
||||
pip3 install loguru && \
|
||||
pip3 install dateparser && \
|
||||
pip3 install connexion && \
|
||||
pip3 install connexion[swagger-ui] && \
|
||||
pip3 install uwsgi && \
|
||||
pip3 install flask-cors && \
|
||||
pip3 install six && \
|
||||
pip3 install pymodbus
|
||||
|
||||
RUN \
|
||||
mkdir -p ${APP_DIR} && \
|
||||
mkdir -p ${CONF_DIR} && \
|
||||
useradd -d ${APP_DIR} -u 1000 user
|
||||
|
||||
COPY *.py ${APP_DIR}/
|
||||
COPY openapi.yaml ${APP_DIR}/
|
||||
COPY server.ini ${CONF_DIR}/
|
||||
|
||||
USER 1000:1000
|
||||
WORKDIR ${APP_DIR}
|
||||
VOLUME ${CONF_DIR}
|
||||
|
||||
EXPOSE 5000
|
||||
EXPOSE 9191
|
||||
|
||||
CMD [ "uwsgi", "./config/server.ini" ]
|
||||
|
||||
|
||||
|
87
api.py
Normal file
87
api.py
Normal file
@ -0,0 +1,87 @@
|
||||
import time
|
||||
import connexion
|
||||
import json
|
||||
import werkzeug
|
||||
import os
|
||||
from loguru import logger
|
||||
from pymodbus.client.sync import ModbusTcpClient
|
||||
from pymodbus.exceptions import ModbusIOException
|
||||
|
||||
|
||||
#with open('/opt/app/config/authservice.key', 'r') as f:
|
||||
# JWT_PRIV_KEY = f.read()
|
||||
|
||||
clients = {
|
||||
"modbus1": "172.16.2.157"
|
||||
}
|
||||
|
||||
clientConn = {}
|
||||
|
||||
class UnknownClientException (Exception): pass
|
||||
|
||||
class UnableToConnectException (Exception): pass
|
||||
|
||||
class ConnectionException (Exception): pass
|
||||
|
||||
class IllegalValueException (Exception): pass
|
||||
|
||||
|
||||
def connect(name):
|
||||
try:
|
||||
if name not in clientConn:
|
||||
clientConn[name] = ModbusTcpClient(clients[name])
|
||||
if not clientConn[name].connect():
|
||||
raise UnableToConnectException()
|
||||
return clientConn[name]
|
||||
except KeyError as e:
|
||||
raise UnknownClientException(e)
|
||||
|
||||
def close(name):
|
||||
try:
|
||||
clientConn[name].close()
|
||||
except KeyError as e:
|
||||
raise UnknownClientException(e)
|
||||
|
||||
|
||||
def listClients():
|
||||
return [ {"name":k, "address":v} for k,v in clients.items() ]
|
||||
|
||||
def createClient(**args):
|
||||
try:
|
||||
body = args["body"]
|
||||
|
||||
name = body["name"]
|
||||
address = body["address"]
|
||||
|
||||
return "Ok"
|
||||
except KeyError as e:
|
||||
raise werkzeug.exceptions.BadRequest("name or address missing")
|
||||
except Exception as e:
|
||||
raise werkzeug.exceptions.BadRequest("{}: {}".format(e.__class__.__name__, str(e)))
|
||||
|
||||
def readCoil(client, address):
|
||||
try:
|
||||
conn = connect(client)
|
||||
res = conn.read_coils(address)
|
||||
if isinstance(res, ModbusIOException):
|
||||
raise ConnectionException()
|
||||
return int(res.bits[0])
|
||||
except Exception as e:
|
||||
raise werkzeug.exceptions.BadRequest("{}: {}".format(e.__class__.__name__, str(e)))
|
||||
finally:
|
||||
close(client)
|
||||
|
||||
def writeCoil(client, address, **args):
|
||||
try:
|
||||
value = args["body"]
|
||||
if value not in [ 0, 1 ]:
|
||||
raise IllegalValueException("Value in body must be 0 or 1")
|
||||
conn = connect(client)
|
||||
res = conn.write_coil(address, value)
|
||||
if isinstance(res, ModbusIOException):
|
||||
raise ConnectionException()
|
||||
return "Ok"
|
||||
except Exception as e:
|
||||
raise werkzeug.exceptions.BadRequest("{}: {}".format(e.__class__.__name__, str(e)))
|
||||
finally:
|
||||
close(client)
|
94
openapi.yaml
Normal file
94
openapi.yaml
Normal file
@ -0,0 +1,94 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: ModbusService
|
||||
version: "0.1"
|
||||
|
||||
paths:
|
||||
/client:
|
||||
get:
|
||||
tags: [ "Client" ]
|
||||
summary: List all configured clients
|
||||
operationId: api.listClients
|
||||
responses:
|
||||
'200':
|
||||
description: All configured clients
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Client'
|
||||
post:
|
||||
tags: [ "Client" ]
|
||||
summary: Create a new client
|
||||
operationId: api.createClient
|
||||
requestBody:
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/Client'
|
||||
responses:
|
||||
'200':
|
||||
description: Client successfully created
|
||||
/action/coil/{client}/{address}:
|
||||
get:
|
||||
tags: [ "Action" ]
|
||||
summary: Return a coil of client
|
||||
operationId: api.readCoil
|
||||
parameters:
|
||||
- name: client
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: address
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: Status of the coil
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: integer
|
||||
put:
|
||||
tags: [ "Action" ]
|
||||
summary: Set a coil of client
|
||||
operationId: api.writeCoil
|
||||
parameters:
|
||||
- name: client
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: address
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: number
|
||||
responses:
|
||||
'200':
|
||||
description: Status of the coil
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: number
|
||||
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Client:
|
||||
description: Modbus Client
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
address:
|
||||
type: string
|
6
server.ini
Normal file
6
server.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[uwsgi]
|
||||
http = :5000
|
||||
wsgi-file = server.py
|
||||
processes = 4
|
||||
stats = :9191
|
||||
|
12
server.py
Normal file
12
server.py
Normal file
@ -0,0 +1,12 @@
|
||||
import connexion
|
||||
from flask_cors import CORS
|
||||
|
||||
# instantiate the webservice
|
||||
app = connexion.App(__name__)
|
||||
app.add_api('openapi.yaml', options = {"swagger_ui": True})
|
||||
|
||||
# CORSify it - otherwise Angular won't accept it
|
||||
CORS(app.app)
|
||||
|
||||
# provide the webservice application to uwsgi
|
||||
application = app.app
|
Loading…
x
Reference in New Issue
Block a user