mirror of
https://gitlab.com/fabinfra/fabaccess/fabfire_adapter.git
synced 2025-03-12 06:41:47 +01:00
updated for latest api
This commit is contained in:
parent
9e4173883d
commit
8ab59e053f
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.8-slim-buster
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip3 install -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
VOLUME /app/config
|
||||||
|
CMD [ "python3", "main.py"]
|
@ -4,11 +4,11 @@ port = 1883
|
|||||||
|
|
||||||
[bffhd]
|
[bffhd]
|
||||||
hostname = "192.168.178.31"
|
hostname = "192.168.178.31"
|
||||||
port = 59661
|
port = 59662
|
||||||
|
|
||||||
[readers]
|
[readers]
|
||||||
[readers.abc]
|
[readers.abc]
|
||||||
id = "111"
|
id = "001"
|
||||||
machine = "urn:fabaccess:resource:Testmachine"
|
machine = "urn:fabaccess:resource:Testmachine"
|
||||||
|
|
||||||
[readers.def]
|
[readers.def]
|
@ -41,26 +41,23 @@ async def connect(host, port, user, pw):
|
|||||||
|
|
||||||
# Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode)
|
# Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode)
|
||||||
client = capnp.TwoPartyClient()
|
client = capnp.TwoPartyClient()
|
||||||
cap = client.bootstrap().cast_as(connection_capnp.Bootstrap)
|
|
||||||
|
|
||||||
# Assemble reader and writer tasks, run in the background
|
# Assemble reader and writer tasks, run in the background
|
||||||
coroutines = [myreader(client, reader), mywriter(client, writer)]
|
coroutines = [myreader(client, reader), mywriter(client, writer)]
|
||||||
asyncio.gather(*coroutines, return_exceptions=True)
|
asyncio.gather(*coroutines, return_exceptions=True)
|
||||||
|
|
||||||
auth = cap.authenticationSystem().authenticationSystem
|
boot = client.bootstrap().cast_as(connection_capnp.Bootstrap)
|
||||||
req = auth.start_request()
|
auth = await boot.createSession("PLAIN").a_wait()
|
||||||
req.request.mechanism = "PLAIN"
|
p = "\0" + user + "\0" + pw
|
||||||
req.request.initialResponse.initial = "\0" + user + "\0" + pw
|
response = await auth.authentication.step(p).a_wait()
|
||||||
rep_prom = req.send()
|
if response.which() == 'successful':
|
||||||
response = await rep_prom.a_wait()
|
return response.successful.session
|
||||||
if response.response.outcome.result == "successful":
|
|
||||||
logging.info("Auth completed successfully")
|
|
||||||
return cap
|
|
||||||
else:
|
else:
|
||||||
logging.info("Auth failed")
|
print("Authentication failed!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def connect_with_fabfire_initial(host, port, uid):
|
async def connect_with_fabfire_initial(host, port, uid):
|
||||||
# Setup SSL context
|
# Setup SSL context
|
||||||
ctx = ssl.create_default_context(
|
ctx = ssl.create_default_context(
|
||||||
@ -81,38 +78,32 @@ async def connect_with_fabfire_initial(host, port, uid):
|
|||||||
|
|
||||||
# Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode)
|
# Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode)
|
||||||
client = capnp.TwoPartyClient()
|
client = capnp.TwoPartyClient()
|
||||||
cap = client.bootstrap().cast_as(connection_capnp.Bootstrap)
|
|
||||||
|
|
||||||
# Assemble reader and writer tasks, run in the background
|
# Assemble reader and writer tasks, run in the background
|
||||||
coroutines = [myreader(client, reader), mywriter(client, writer)]
|
coroutines = [myreader(client, reader), mywriter(client, writer)]
|
||||||
asyncio.gather(*coroutines, return_exceptions=True)
|
asyncio.gather(*coroutines, return_exceptions=True)
|
||||||
|
|
||||||
auth = cap.authenticationSystem().authenticationSystem
|
boot = client.bootstrap().cast_as(connection_capnp.Bootstrap)
|
||||||
req = auth.start_request()
|
auth = await boot.createSession("X-FABFIRE").a_wait()
|
||||||
req.request.mechanism = "X-FABFIRE"
|
response = await auth.authentication.step(uid).a_wait()
|
||||||
req.request.initialResponse.initial = uid
|
logging.debug(f"got response type: {response.which()}")
|
||||||
rep_prom = req.send()
|
if response.which() == "challenge":
|
||||||
response = await rep_prom.a_wait()
|
logging.debug(f"challenge: {response.challenge}")
|
||||||
logging.debug(f"got response type: {response.response.which()}")
|
return auth, response.challenge
|
||||||
if response.response.which() == "challence":
|
|
||||||
logging.debug(f"challenge: {response.response.challence}")
|
|
||||||
return auth, response.response.challence, cap
|
|
||||||
else:
|
else:
|
||||||
logging.error(f"Auth failed: {response.response.outcome.result}, additional info: {response.response.outcome.helpText}")
|
logging.error(f"Auth failed: {response.failed.code}, additional info: {response.failed.additionalData}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def connect_with_fabfire_step(auth, msg):
|
async def connect_with_fabfire_step(auth, msg):
|
||||||
req = auth.step_request()
|
response = await auth.authentication.step(msg).a_wait()
|
||||||
req.response = msg
|
if response.which() == "challenge":
|
||||||
rep_prom = req.send()
|
logging.debug(f"challenge: {response.challenge}")
|
||||||
response = await rep_prom.a_wait()
|
return response.challenge, None # auth cap, challenge, not done
|
||||||
if response.response.which() == "challence":
|
elif response.which() == "successful":
|
||||||
logging.debug(f"challenge: {response.response.challence}")
|
logging.info(f"Auth completed successfully! Got additional Data: {response.successful.additionalData}")
|
||||||
return response.response.challence, False # auth cap, challenge, not done
|
return response.successful.additionalData, response.successful.session # dont care, message, we are done
|
||||||
elif response.response.outcome.result == "successful":
|
|
||||||
logging.info(f"Auth completed successfully! Got additional Data: {response.response.outcome.additionalData.additional}")
|
|
||||||
return response.response.outcome.additionalData.additional, True # dont care, message, we are done
|
|
||||||
else:
|
else:
|
||||||
logging.error(f"Auth failed: {response.response.outcome.result}, additional info: {response.response.outcome.helpText}")
|
logging.error(f"Auth failed: {response.failed.code}, additional info: {response.failed.additionalData}")
|
||||||
return None
|
return None
|
||||||
|
10
host.py
Normal file
10
host.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Host:
|
||||||
|
hostname: str
|
||||||
|
port: int
|
||||||
|
username: Optional[str] = None
|
||||||
|
password: Optional[str] = None
|
54
main.py
54
main.py
@ -4,57 +4,11 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Optional
|
|
||||||
import asyncio_mqtt
|
|
||||||
from asyncio_mqtt import Client, MqttError
|
from asyncio_mqtt import Client, MqttError
|
||||||
import fabapi
|
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
|
from host import Host
|
||||||
@dataclass(frozen=True)
|
from reader import Reader
|
||||||
class Host:
|
|
||||||
hostname: str
|
|
||||||
port: int
|
|
||||||
username: Optional[str] = None
|
|
||||||
password: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Reader:
|
|
||||||
def __init__(self, reader_id: str, machine_urn: str, bffhd_address: Host):
|
|
||||||
self.reader_id = reader_id
|
|
||||||
self.machine_urn = machine_urn
|
|
||||||
self.bffhd_address = bffhd_address
|
|
||||||
self.is_authenticated = False
|
|
||||||
self.auth_cap = None
|
|
||||||
self.bootstrap_cap = None
|
|
||||||
|
|
||||||
async def handle_messages(self, messages, client: asyncio_mqtt.Client):
|
|
||||||
async for message in messages:
|
|
||||||
response_for_reader = None
|
|
||||||
if not self.auth_cap:
|
|
||||||
self.auth_cap, response_for_reader, self.bootstrap_cap = await fabapi.connect_with_fabfire_initial(
|
|
||||||
self.bffhd_address.hostname, self.bffhd_address.port, message.payload)
|
|
||||||
elif not self.is_authenticated:
|
|
||||||
response_for_reader, self.is_authenticated = await fabapi.connect_with_fabfire_step(self.auth_cap,
|
|
||||||
message.payload)
|
|
||||||
if self.is_authenticated:
|
|
||||||
await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2,
|
|
||||||
retain=False)
|
|
||||||
ms = await self.bootstrap_cap.machineSystem().a_wait()
|
|
||||||
info = await ms.machineSystem.info().a_wait()
|
|
||||||
ma = await info.info.getMachineURN(f"{self.machine_urn}").a_wait()
|
|
||||||
if ma.machine.state == "inUse":
|
|
||||||
await ma.machine.inuse.giveBack().a_wait()
|
|
||||||
else:
|
|
||||||
await ma.machine.use.use().a_wait()
|
|
||||||
self.is_authenticated = False
|
|
||||||
self.bootstrap_cap = None
|
|
||||||
self.auth_cap = None
|
|
||||||
response_for_reader = None
|
|
||||||
|
|
||||||
if response_for_reader:
|
|
||||||
await client.publish(f"/cmnd/reader/{self.reader_id}", payload=response_for_reader, qos=2, retain=False)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_adapter(mqtt_host: Host, readers: list[Reader]):
|
async def run_adapter(mqtt_host: Host, readers: list[Reader]):
|
||||||
@ -77,6 +31,8 @@ async def run_adapter(mqtt_host: Host, readers: list[Reader]):
|
|||||||
messages = await stack.enter_async_context(manager)
|
messages = await stack.enter_async_context(manager)
|
||||||
task = asyncio.create_task(reader.handle_messages(messages, client))
|
task = asyncio.create_task(reader.handle_messages(messages, client))
|
||||||
tasks.add(task)
|
tasks.add(task)
|
||||||
|
await client.publish(f"/cmnd/reader/{reader.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2,
|
||||||
|
retain=False) # reset readers
|
||||||
logging.info(f"Registered handler for reader {reader.reader_id}")
|
logging.info(f"Registered handler for reader {reader.reader_id}")
|
||||||
|
|
||||||
# Messages that doesn't match a filter will get logged here
|
# Messages that doesn't match a filter will get logged here
|
||||||
@ -117,7 +73,7 @@ async def main():
|
|||||||
# if the connection is lost.
|
# if the connection is lost.
|
||||||
reconnect_interval = 3 # [seconds]
|
reconnect_interval = 3 # [seconds]
|
||||||
|
|
||||||
config = toml.load("config.toml")
|
config = toml.load("config/config.toml")
|
||||||
|
|
||||||
mqtt_host = Host(config["mqtt"]["hostname"], config["mqtt"]["port"], config["mqtt"].setdefault("username", None),
|
mqtt_host = Host(config["mqtt"]["hostname"], config["mqtt"]["port"], config["mqtt"].setdefault("username", None),
|
||||||
config["mqtt"].setdefault("password", None))
|
config["mqtt"].setdefault("password", None))
|
||||||
|
66
reader.py
Normal file
66
reader.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import asyncio_mqtt
|
||||||
|
|
||||||
|
import fabapi
|
||||||
|
from host import Host
|
||||||
|
from timer import Timer
|
||||||
|
|
||||||
|
|
||||||
|
class Reader:
|
||||||
|
def __init__(self, reader_id: str, machine_urn: str, bffhd_address: Host):
|
||||||
|
self.reader_id = reader_id
|
||||||
|
self.machine_urn = machine_urn
|
||||||
|
self.bffhd_address = bffhd_address
|
||||||
|
self.auth_cap = None
|
||||||
|
self.session = None
|
||||||
|
self.timeout_timer = None
|
||||||
|
|
||||||
|
async def handle_messages(self, messages, client: asyncio_mqtt.Client):
|
||||||
|
try:
|
||||||
|
async for message in messages:
|
||||||
|
response_for_reader = None
|
||||||
|
if not self.auth_cap:
|
||||||
|
self.timeout_timer = Timer(10, lambda: self.handle_timeout(client))
|
||||||
|
self.auth_cap, response_for_reader = await fabapi.connect_with_fabfire_initial(
|
||||||
|
self.bffhd_address.hostname, self.bffhd_address.port, message.payload)
|
||||||
|
elif not self.session:
|
||||||
|
response_for_reader, self.session = await fabapi.connect_with_fabfire_step(self.auth_cap,
|
||||||
|
message.payload)
|
||||||
|
if self.session:
|
||||||
|
self.timeout_timer.cancel()
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2,
|
||||||
|
retain=False)
|
||||||
|
info = await self.session.machineSystem.info().a_wait()
|
||||||
|
ma = await info.info.getMachineURN(f"{self.machine_urn}").a_wait()
|
||||||
|
if ma.state == "inUse":
|
||||||
|
await ma.inuse.giveBack().a_wait()
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"message","MssgID":0,"AddnTxt":""}', qos=2,
|
||||||
|
retain=False)
|
||||||
|
else:
|
||||||
|
await ma.use.use().a_wait()
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}",
|
||||||
|
payload='{"Cmd":"message","MssgID":3,"AddnTxt":""}', qos=2,
|
||||||
|
retain=False)
|
||||||
|
self.session = None
|
||||||
|
self.auth_cap = None
|
||||||
|
response_for_reader = None
|
||||||
|
|
||||||
|
if response_for_reader:
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}", payload=response_for_reader, qos=2, retain=False)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Caught exception {e}")
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2,
|
||||||
|
retain=False)
|
||||||
|
self.session = None
|
||||||
|
self.auth_cap = None
|
||||||
|
|
||||||
|
async def handle_timeout(self, client):
|
||||||
|
await client.publish(f"/cmnd/reader/{self.reader_id}", payload='{"Cmd":"haltPICC"}', qos=2,
|
||||||
|
retain=False)
|
||||||
|
logging.critical(f"authentication timed out on reader {self.reader_id}")
|
||||||
|
self.auth_cap = None
|
||||||
|
self.session = None
|
18
timer.py
Normal file
18
timer.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import asyncio_mqtt
|
||||||
|
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __init__(self, timeout: int, callback: Callable):
|
||||||
|
self._timeout = timeout
|
||||||
|
self._callback = callback
|
||||||
|
self._task = asyncio.ensure_future(self._job())
|
||||||
|
|
||||||
|
async def _job(self):
|
||||||
|
await asyncio.sleep(self._timeout)
|
||||||
|
await self._callback()
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self._task.cancel()
|
Loading…
x
Reference in New Issue
Block a user