mirror of
https://gitlab.com/sfz.aalen/infra/fabaccess.git
synced 2025-03-12 15:01:47 +01:00
Merge branch 'NewApproach' into 'main'
New approach See merge request luca_lutz/fabaccess!2
This commit is contained in:
commit
0a06c36dd1
162
.gitignore
vendored
162
.gitignore
vendored
@ -1,160 +1,2 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
/sql
|
||||||
__pycache__/
|
/.env
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
|
||||||
.pdm.toml
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
@ -14,8 +14,10 @@ DB_USERNAME=postgres
|
|||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
DB_DATABASE=fab_access
|
DB_DATABASE=fab_access
|
||||||
|
|
||||||
|
MQTT_PORT=1883
|
||||||
MQTT_USERNAME=user
|
MQTT_USERNAME=user
|
||||||
MQTT_PASSWORD=password
|
MQTT_PASSWORD=password
|
||||||
MQTT_BROKER=mqtt
|
MQTT_BROKER=mqtt
|
||||||
MQTT_CLIENT=FabMan
|
MQTT_CLIENT=FabMan
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -21,7 +21,10 @@ services:
|
|||||||
MQTT_PASSWORD: ${MQTT_PASSWORD:?err}
|
MQTT_PASSWORD: ${MQTT_PASSWORD:?err}
|
||||||
MQTT_BROKER: ${MQTT_BROKER:?err}
|
MQTT_BROKER: ${MQTT_BROKER:?err}
|
||||||
MQTT_CLIENT: ${MQTT_CLIENT:?err}
|
MQTT_CLIENT: ${MQTT_CLIENT:?err}
|
||||||
|
# Various
|
||||||
|
MACHINES: ${MACHINES:?err}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
volumes:
|
volumes:
|
||||||
@ -30,8 +33,11 @@ services:
|
|||||||
POSTGRES_DB: ${DB_DATABASE}
|
POSTGRES_DB: ${DB_DATABASE}
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
|
||||||
mqtt:
|
mqtt:
|
||||||
image: eclipse-mosquitto:2
|
image: eclipse-mosquitto:2
|
||||||
|
ports:
|
||||||
|
- 1883:1883
|
||||||
environment:
|
environment:
|
||||||
MQTT_USERNAME: ${MQTT_USERNAME}
|
MQTT_USERNAME: ${MQTT_USERNAME}
|
||||||
MQTT_PASSWORD: ${MQTT_PASSWORD}
|
MQTT_PASSWORD: ${MQTT_PASSWORD}
|
||||||
|
@ -1,33 +1,27 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
def _read_from_env(key: str, default: str = None) -> str:
|
class Config:
|
||||||
if (value := os.environ.get(key, default)) is not None:
|
|
||||||
return value
|
|
||||||
raise Exception(f'[Config] Error: Cannot find required value {key}')
|
|
||||||
|
|
||||||
def get_keycloak_config():
|
def _read_from_env(key: str, default: str = None) -> str:
|
||||||
return {
|
if (value := os.environ.get(key, default)) is not None:
|
||||||
'server_url': _read_from_env('KEYCLOAK_URL'),
|
return value
|
||||||
'username': _read_from_env('KEYCLOAK_USERNAME'),
|
raise Exception(f'[Config] Error: Cannot find required value {key}')
|
||||||
'password': _read_from_env('KEYCLOAK_PASSWORD'),
|
|
||||||
'realm_name': _read_from_env('REALM'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_database_config():
|
mqtt_broker = _read_from_env('MQTT_BROKER')
|
||||||
return {
|
mqtt_port = int(_read_from_env('MQTT_PORT','1883'))
|
||||||
'host': _read_from_env('DB_HOSTNAME'),
|
mqtt_client_id = _read_from_env('MQTT_CLIENT')
|
||||||
'user': _read_from_env('DB_USERNAME'),
|
mqtt_user_name = _read_from_env('MQTT_USERNAME')
|
||||||
'port': int(_read_from_env('DB_PORT', '3306')),
|
mqtt_password = _read_from_env('MQTT_PASSWORD')
|
||||||
'password': _read_from_env('DB_PASSWORD'),
|
|
||||||
'dbname': _read_from_env('DB_DATABASE'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_mqtt_config():
|
keycloak_url = _read_from_env('KEYCLOAK_URL')
|
||||||
print("port", int(_read_from_env('MQTT_PORT', '1883')))
|
keycloak_user_name = _read_from_env('KEYCLOAK_USER_NAME')
|
||||||
return {
|
keycloak_password = _read_from_env('KEYCLOAK_USER_PW')
|
||||||
'client_name': _read_from_env('MQTT_CLIENT'),
|
keycloak_realm = _read_from_env('KEYCLOAK_REALM')
|
||||||
'username': _read_from_env('MQTT_USERNAME', ''),
|
|
||||||
'password': _read_from_env('MQTT_PASSWORD', ''),
|
db_host_name = _read_from_env('DB_HOSTNAME')
|
||||||
'broker': _read_from_env('MQTT_BROKER'),
|
db_user_name = _read_from_env('DB_USERNAME')
|
||||||
'port': int(_read_from_env('MQTT_PORT', '1883')),
|
db_password = _read_from_env('DB_PASSWORD')
|
||||||
}
|
db_database = _read_from_env('DB_DATABASE')
|
||||||
|
db_port = int(_read_from_env('DB_PORT','5432'))
|
||||||
|
|
||||||
|
machines = _read_from_env('MACHINES')
|
45
fab_access/keycloak_handler.py
Normal file
45
fab_access/keycloak_handler.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from config import Config
|
||||||
|
from keycloak import KeycloakAdmin
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakHandler:
|
||||||
|
Config.keycloak_password
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def login():
|
||||||
|
KeycloakHandler.admin = KeycloakAdmin(
|
||||||
|
server_url=Config.keycloak_url,
|
||||||
|
username=Config.keycloak_user_name,
|
||||||
|
password=Config.keycloak_password,
|
||||||
|
realm_name=Config.keycloak_realm,
|
||||||
|
verify=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_user_by_card_id(card_id):
|
||||||
|
users = KeycloakHandler.admin.get_users()
|
||||||
|
# Filter not working for Attributes because of multidimensional JSON
|
||||||
|
|
||||||
|
user = [user for user in users if "attributes" in user and "FabCard" in user["attributes"] and user["attributes"]["FabCard"] == [card_id]]
|
||||||
|
print(f'Found {len(user)} user(s) with card_id: {card_id}')
|
||||||
|
|
||||||
|
match len(user):
|
||||||
|
case 0:
|
||||||
|
return None
|
||||||
|
case 1:
|
||||||
|
print(f'FabCard matches with user {user[0]["username"]}')
|
||||||
|
return user[0]
|
||||||
|
case other:
|
||||||
|
print(f'Error! too many users with card_id: {card_id}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def user_is_privileged(username):
|
||||||
|
groups = KeycloakHandler.admin.get_user_groups(user_id=KeycloakHandler.admin.get_user_id(username))
|
||||||
|
groups = [group['name'] for group in groups]
|
||||||
|
|
||||||
|
if 'Mentoren' in groups:
|
||||||
|
print('Overrided becouse of "Mentor" group')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
@ -1,154 +1,100 @@
|
|||||||
from keycloak import KeycloakAdmin
|
|
||||||
import json
|
import json
|
||||||
import psycopg
|
|
||||||
import os
|
import os
|
||||||
from paho.mqtt.client import Client as MQTTClient
|
from config import Config
|
||||||
|
from mqtt_client import MqttHandler
|
||||||
|
from keycloak_handler import KeycloakHandler
|
||||||
|
from sql_handler import SQLHandler
|
||||||
|
|
||||||
import config
|
def has_permission(user_permissions, machine_id):
|
||||||
from mqtt_helper import MQTTHelper
|
parsed_permissions = [permission.split('.') for permission in user_permissions]
|
||||||
|
parsed_machine_id = machine_id.split('.')
|
||||||
|
|
||||||
conn: psycopg.Connection
|
for permission in parsed_permissions:
|
||||||
keycloak_admin: KeycloakAdmin
|
missmatch = False
|
||||||
|
for i, id_sequence in enumerate(parsed_machine_id):
|
||||||
|
if permission[i] == '*':
|
||||||
|
return True
|
||||||
|
if permission[i] != id_sequence:
|
||||||
|
missmatch = True
|
||||||
|
break
|
||||||
|
if not missmatch:
|
||||||
|
return True
|
||||||
|
|
||||||
def publish(client, topic,msg):
|
return False
|
||||||
result = client.publish(topic, msg)
|
|
||||||
status: 0 | 1 = result[0]
|
|
||||||
if status == 0:
|
|
||||||
#print(f"Send `{msg}` to topic `{topic}`")
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
print(f"Failed to send message to topic {topic}")
|
|
||||||
|
|
||||||
def changePlug(client: MQTTClient, plug_id: str, is_running: bool):
|
def gen_display_name(user):
|
||||||
publish(client,f"/FabLogging/{plug_id}/POWER", is_running)
|
# display names must be not longer than 8 chrs
|
||||||
cmd = "On" if is_running else "Off"
|
if 'firstName' in user.keys() and 'lastName' in user.keys():
|
||||||
publish(client, f"/aktoren/{plug_id}/cmnd/POWER", cmd)
|
full_name = f'{user["firstName"]} {user["lastName"]}'
|
||||||
|
if len(full_name) > 8:
|
||||||
def changeDisplay(client: MQTTClient, reader_id: str, status: int, fab_card_id: str):
|
display_name = f'{user["firstName"][0]}.{user["lastName"][:6]}'
|
||||||
publish(client, f"/cmnd/reader/{reader_id}", '{"Cmd": "message", "MssgID": %s , "ClrTxt":"" , "AddnTxt":"%s"}' % (status, fab_card_id))
|
else:
|
||||||
|
|
||||||
def hasPermission(UserPermsJSON, PermissionPath):
|
|
||||||
# ToDo: refactor this
|
|
||||||
permission = False
|
|
||||||
error = 0
|
|
||||||
MachinePermArray = PermissionPath.split(".")
|
|
||||||
for UserPerm in UserPermsJSON:
|
|
||||||
#print(f"check {UserPerm}")
|
|
||||||
UserPermArray = UserPerm.split(".")
|
|
||||||
x = 0
|
|
||||||
for UserPermPart in UserPermArray:
|
|
||||||
if(error == 0):
|
|
||||||
#print(f"Compare {UserPermPart} and {MachinePermArray[x]}")
|
|
||||||
if not (MachinePermArray[x] == UserPermPart):
|
|
||||||
if(UserPermPart == "*"):
|
|
||||||
#print("* regelt")
|
|
||||||
permission = True
|
|
||||||
else:
|
|
||||||
error = 1
|
|
||||||
#print(f"MISmatch between {MachinePermArray[x]} and {UserPermPart}")
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
#print(f"Match between {MachinePermArray[x]} and {UserPermPart}")
|
|
||||||
x = x + 1
|
|
||||||
if(error == 1):
|
|
||||||
pass
|
|
||||||
#print("Error")
|
|
||||||
else:
|
|
||||||
#print("Hurra")
|
|
||||||
permission = True
|
|
||||||
error = 0
|
|
||||||
return permission
|
|
||||||
|
|
||||||
def handle_msg(client: MQTTClient, userdata, msg):
|
|
||||||
global conn
|
|
||||||
|
|
||||||
payload = msg.payload.decode()
|
|
||||||
print(f"Received `{payload}` from `{msg.topic}` topic")
|
|
||||||
|
|
||||||
fab_card_id = json.loads(msg.payload.decode())["UID"]
|
|
||||||
reader_id = msg.topic.split("/")[-1]
|
|
||||||
|
|
||||||
# check user exists
|
|
||||||
users = keycloak_admin.get_users({ 'attributes': { 'FabCard': fab_card_id } })
|
|
||||||
if (len(users) != 1):
|
|
||||||
print(f'Found {len(users)} users with {fab_card_id=}')
|
|
||||||
changeDisplay(client, reader_id, 16, fab_card_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
# retrieve user attributes from DB
|
|
||||||
user = users[0]
|
|
||||||
print(f"FabCard matches with user {user['username']}")
|
|
||||||
with conn.cursor() as cursor:
|
|
||||||
cursor.execute(f'SELECT PlugName,PermissionPath,Status,LastUser FROM ReaderPlug WHERE ReaderID=?;', (reader_id,))
|
|
||||||
res = cursor.fetchone()
|
|
||||||
if res is None:
|
|
||||||
print(f'Error fetching card info from db for {reader_id=}')
|
|
||||||
return
|
|
||||||
plug_id, permission_path, is_running, last_user = res
|
|
||||||
|
|
||||||
# check permissions
|
|
||||||
user_permissions = user['attributes']['FabPermissions'][0]
|
|
||||||
if not hasPermission(user_permissions, permission_path):
|
|
||||||
changeDisplay(client, reader_id, 7, "")
|
|
||||||
return
|
|
||||||
|
|
||||||
# check for mentor
|
|
||||||
if is_running and last_user != user['username']:
|
|
||||||
try:
|
try:
|
||||||
if (keycloak_admin.get_user_groups(user_id=user['id'])[0]['name'] == 'Mentoren'):
|
display_name = user['username'][:8]
|
||||||
print('Overrided becouse of "Mentor" Group')
|
except KeyError:
|
||||||
else:
|
print('user has no username')
|
||||||
print(f'Bereits benutzt von {last_user}')
|
return 'Error'
|
||||||
error = True
|
return(display_name)
|
||||||
except IndexError:
|
|
||||||
print("No groups available")
|
|
||||||
error = True
|
|
||||||
if(error):
|
|
||||||
changeDisplay(client, reader_id, 9, last_user)
|
|
||||||
|
|
||||||
# toggle machine state
|
def handle_request(msg, client):
|
||||||
print(f"Turn Plug {'off' if is_running else 'on'}")
|
print('')
|
||||||
changePlug(client, plug_id, is_running)
|
print(f'Received `{msg.payload.decode()}` from `{msg.topic}` topic')
|
||||||
publish(client, f"/FabLogging/{plug_id}/USER", user['username'])
|
fabcard_id = json.loads(msg.payload.decode())['UID']
|
||||||
|
reader_id = msg.topic.split('/')[-1]
|
||||||
|
|
||||||
|
KeycloakHandler.login()
|
||||||
|
user = KeycloakHandler.get_user_by_card_id(fabcard_id)
|
||||||
|
if not user:
|
||||||
|
MqttHandler.print_to_display(reader_id, 16, fabcard_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
db_data = SQLHandler.get_machine_data(reader_id)
|
||||||
|
|
||||||
|
machine_id = db_data["machine_id"]
|
||||||
|
last_user = db_data["last_user"]
|
||||||
|
machine_status = db_data["machine_status"]
|
||||||
|
plug_id = db_data["plug_id"]
|
||||||
|
|
||||||
# ToDo: refactor display name construction
|
|
||||||
try:
|
try:
|
||||||
firstCombo = user['firstName']+" "
|
user_permissions = json.loads(user['attributes']['FabPermissions'][0])
|
||||||
DisplayUser = firstCombo+user['lastName']
|
|
||||||
if(len(firstCombo) >= 7):
|
|
||||||
DisplayUser = user['firstName'][:7]
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
DisplayUser = user['username']
|
print(f'user with id {fabcard_id} is missing FabPermissions attr')
|
||||||
if(len(DisplayUser) > 7):
|
except IndexError:
|
||||||
DisplayUser = DisplayUser[0:7] + "." + DisplayUser[7+1: ]
|
print(f'user with id {fabcard_id} is missing FabPermissions attr')
|
||||||
DisplayUser = DisplayUser[:8]
|
|
||||||
|
|
||||||
changeDisplay(client, reader_id, 20, f"Login\n{DisplayUser}" if is_running else "Bitte anmelden")
|
if not has_permission(user_permissions, machine_id):
|
||||||
|
print(f"user with id {fabcard_id} is missing {machine_id}")
|
||||||
|
MqttHandler.print_to_display(reader_id, 7, '')
|
||||||
|
return
|
||||||
|
|
||||||
|
username = user['username']
|
||||||
|
display_name = gen_display_name(user)
|
||||||
|
|
||||||
|
if not machine_status:
|
||||||
|
print(f'Turn Plug {plug_id} on')
|
||||||
|
MqttHandler.switch_plug(plug_id, 1)
|
||||||
|
MqttHandler.print_to_display(reader_id, 20, f'Login\n{display_name}')
|
||||||
|
else:
|
||||||
|
if not (username == last_user or KeycloakHandler.user_is_privileged(username)):
|
||||||
|
MqttHandler.print_to_display(reader_id, 9, last_user)
|
||||||
|
return
|
||||||
|
print(f'Turn Plug {plug_id} off')
|
||||||
|
MqttHandler.switch_plug(plug_id, 0)
|
||||||
|
MqttHandler.print_to_display(reader_id, 20, f'Bitte anmelden')
|
||||||
|
|
||||||
|
SQLHandler.update_machine(reader_id, username, machine_status)
|
||||||
|
MqttHandler.publish(f'/FabLogging/{plug_id}/USER', username)
|
||||||
|
|
||||||
# write new status to db
|
|
||||||
with conn.cursor() as cursor:
|
|
||||||
cursor.execute(
|
|
||||||
f'UPDATE ReaderPlug SET Status=?, LastUser="?" WHERE ReaderID="?";',
|
|
||||||
(is_running, user['username'], reader_id),
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global conn
|
MqttHandler.setup(handle_request)
|
||||||
global keycloak_admin
|
KeycloakHandler.login()
|
||||||
|
SQLHandler.setup()
|
||||||
mqtt_client = MQTTHelper(**config.get_mqtt_config())
|
|
||||||
mqtt_client.subscribe("/rfid_reader/#", handle_msg)
|
|
||||||
mqtt_client.loop_forever()
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn = psycopg.connect(**config.get_database_config())
|
|
||||||
except mariadb.Error as e:
|
|
||||||
print(f"Error connecting to MariaDB Platform")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
keycloak_admin = KeycloakAdmin(**config.get_keycloak_config(), verify=True)
|
|
||||||
|
|
||||||
|
MqttHandler.connect_mqtt()
|
||||||
|
MqttHandler.subscribe("/rfid_reader/#")
|
||||||
|
SQLHandler.init_db()
|
||||||
|
MqttHandler.loop()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
62
fab_access/mqtt_client.py
Normal file
62
fab_access/mqtt_client.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from paho.mqtt import client as mqtt_client
|
||||||
|
from config import Config
|
||||||
|
import json
|
||||||
|
|
||||||
|
class MqttHandler:
|
||||||
|
@staticmethod
|
||||||
|
def setup(msg_handler):
|
||||||
|
MqttHandler.msg_handler = msg_handler
|
||||||
|
MqttHandler.client = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connect_mqtt():
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
if rc == 0:
|
||||||
|
print('Connected to MQTT Broker!')
|
||||||
|
else:
|
||||||
|
print('Failed to connect, return code %d\n', rc)
|
||||||
|
|
||||||
|
MqttHandler.client = mqtt_client.Client(Config.mqtt_client_id)
|
||||||
|
MqttHandler.client.username_pw_set('admin', 'user')
|
||||||
|
MqttHandler.client.on_connect = on_connect
|
||||||
|
MqttHandler.client.username_pw_set(Config.mqtt_user_name, Config.mqtt_password)
|
||||||
|
MqttHandler.client.connect(Config.mqtt_broker, Config.mqtt_port)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def publish(topic, msg):
|
||||||
|
result = MqttHandler.client.publish(topic, msg)
|
||||||
|
# result: [0, 1]
|
||||||
|
status = result[0]
|
||||||
|
if status == 0:
|
||||||
|
#print(f'Send `{msg}` to topic `{topic}`')
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print(f'Failed to send message to topic {topic}')
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def subscribe(topic):
|
||||||
|
def on_message(client, userdata, msg):
|
||||||
|
MqttHandler.msg_handler(msg, client)
|
||||||
|
|
||||||
|
MqttHandler.client.subscribe(topic)
|
||||||
|
MqttHandler.client.on_message = on_message
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def loop():
|
||||||
|
MqttHandler.client.loop_forever()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def switch_plug(PlugID, state):
|
||||||
|
MqttHandler.publish(f'/FabLogging/{PlugID}/POWER', 1 if state else 0)
|
||||||
|
MqttHandler.publish(f'/aktoren/{PlugID}/cmnd/POWER', 'On' if state else 'OFF')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def print_to_display(MachineID, status, text):
|
||||||
|
message = {
|
||||||
|
'Cmd': 'message',
|
||||||
|
'MssgID': status,
|
||||||
|
'ClrTxt': '',
|
||||||
|
'AddnTxt': text,
|
||||||
|
}
|
||||||
|
MqttHandler.publish(f'/cmnd/reader/{MachineID}', json.dumps(message))
|
82
fab_access/sql_handler.py
Normal file
82
fab_access/sql_handler.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import psycopg2
|
||||||
|
from psycopg2 import sql, extensions
|
||||||
|
import json
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
class SQLHandler:
|
||||||
|
@staticmethod
|
||||||
|
def setup():
|
||||||
|
SQLHandler.cursor = None
|
||||||
|
SQLHandler.conn = None
|
||||||
|
SQLHandler.conn = psycopg2.connect(host=Config.db_host_name, user=Config.db_user_name, port=Config.db_port, password=Config.db_password, dbname=Config.db_database)
|
||||||
|
# get the isolation leve for autocommit
|
||||||
|
autocommit = extensions.ISOLATION_LEVEL_AUTOCOMMIT
|
||||||
|
print ("ISOLATION_LEVEL_AUTOCOMMIT:", extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||||
|
|
||||||
|
# set the isolation level for the connection's cursors
|
||||||
|
# will raise ActiveSqlTransaction exception otherwise
|
||||||
|
SQLHandler.conn.set_isolation_level( autocommit )
|
||||||
|
SQLHandler.cursor = SQLHandler.conn.cursor()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_machine_data(reader_id):
|
||||||
|
# TODO Database
|
||||||
|
# - get machine_id from database
|
||||||
|
# - get last_user from database
|
||||||
|
# - get machine_status from database, is 0 for off, 1 for on
|
||||||
|
# - get plug_id from database
|
||||||
|
SQLHandler.cursor.execute("SELECT * FROM readerplug WHERE readerplug.reader_id = %s;", (reader_id,))
|
||||||
|
data = [row for row in SQLHandler.cursor.fetchall()]
|
||||||
|
if(len(data) > 0):
|
||||||
|
return {
|
||||||
|
'machine_id': data[0][2],
|
||||||
|
'last_user': data[0][4],
|
||||||
|
'machine_status': data[0][3],
|
||||||
|
'plug_id': data[0][0]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print("No maching Card Reader found in db")
|
||||||
|
return "Error"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_machine(reader_id,last_user,machine_status):
|
||||||
|
SQLHandler.cursor.execute("UPDATE readerplug SET machine_status = %s WHERE readerplug.reader_id = %s", (False if machine_status else True,reader_id))
|
||||||
|
SQLHandler.cursor.execute("UPDATE readerplug SET last_user = %s WHERE readerplug.reader_id = %s", (last_user,reader_id))
|
||||||
|
SQLHandler.conn.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_db():
|
||||||
|
SQLHandler.cursor.execute("SELECT datname FROM pg_database;")
|
||||||
|
dbs = [row[0] for row in SQLHandler.cursor.fetchall()]
|
||||||
|
if not (Config.db_database in dbs):
|
||||||
|
print(f"Missing database ({Config.db_database}) -> creating new db")
|
||||||
|
SQLHandler.cursor.execute(sql.SQL("CREATE DATABASE {};").format(sql.Identifier( Config.db_database )))
|
||||||
|
else:
|
||||||
|
print(f"Found DB {Config.db_database} -> Using existing one")
|
||||||
|
|
||||||
|
SQLHandler.cursor.execute("SELECT * FROM pg_catalog.pg_tables\
|
||||||
|
WHERE schemaname != 'pg_catalog' AND \
|
||||||
|
schemaname != 'information_schema';")
|
||||||
|
tables = [row[1] for row in SQLHandler.cursor.fetchall()]
|
||||||
|
if not ("readerplug" in tables):
|
||||||
|
print("Missing table -> creating new table in db")
|
||||||
|
SQLHandler.cursor.execute("\
|
||||||
|
CREATE TABLE readerplug (\
|
||||||
|
reader_id int NOT NULL, \
|
||||||
|
plug_id varchar(255) NOT NULL, \
|
||||||
|
machine_id varchar(255) NOT NULL, \
|
||||||
|
machine_status boolean NOT NULL, \
|
||||||
|
last_user varchar(255) NOT NULL \
|
||||||
|
)")
|
||||||
|
else:
|
||||||
|
print("Found Table -> Using existing one")
|
||||||
|
SQLHandler.conn.commit()
|
||||||
|
|
||||||
|
SQLHandler.cursor.execute("SELECT * FROM readerplug;")
|
||||||
|
if(len(SQLHandler.cursor.fetchall()) < 1):
|
||||||
|
print("Found no machines in table, adding machines from config")
|
||||||
|
machines = json.loads(Config.machines)
|
||||||
|
for machine in machines:
|
||||||
|
SQLHandler.cursor.execute("INSERT INTO readerplug (reader_id, plug_id, machine_id, machine_status, last_user) VALUES (%s, %s, %s, False, 'no_user');", (machine[0],machine[1],machine[2]))
|
||||||
|
SQLHandler.conn.commit()
|
@ -1,4 +1,4 @@
|
|||||||
keycloak_wrapper
|
keycloak_wrapper
|
||||||
paho-mqtt
|
paho-mqtt
|
||||||
psycopg[binary]
|
psycopg2-binary
|
||||||
python-keycloak
|
python-keycloak
|
Loading…
x
Reference in New Issue
Block a user