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