Add MQTT and gateway discovery

This commit is contained in:
DasMoorhuhn 2024-07-04 01:35:38 +02:00
parent e162948cb8
commit d7b3303e50
9 changed files with 174 additions and 54 deletions

View File

@ -45,6 +45,9 @@ RUN apk add --no-cache sudo bluez tzdata
ENV TZ=Europe/Berlin
ENV DOCKER=true
ENV API=false
ENV NAME=ATC_MiThermometer_Gateway
ENV VERSION=24-07-03
ENV MODE=1
# Copy pips from the pip build stage
COPY --from=pip_build_stage /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages

View File

@ -5,7 +5,6 @@ env > .env
if [ "$API" = true ]; then
python3.12 api_endpoints.py &
sleep 1
python3.12 find_gateways.py &
fi
python3.12 main.py

View File

@ -5,3 +5,4 @@ requests
flask
flask_cors
FindMyIP
paho-mqtt

View File

@ -4,6 +4,7 @@ from flask import jsonify
from flask import send_from_directory
from flask_cors import CORS
from flask_cors import cross_origin
from check_update import check_for_update
class API:
@ -17,43 +18,44 @@ class API:
self.app.config['CORS_HEADERS'] = 'Content-Type'
# --------Static Routes-------
@self.app.route('/')
@self.app.route('/api')
@cross_origin()
def serve_root():
workdir, filename = os.path.split(os.path.abspath(__file__))
update = check_for_update()
root_dict = {
"version": "24-07-03",
"mode": 1,
"version": {
"version": os.getenv('VERSION'),
"update_available": update.update_available if update is not None else None,
"up_to_date": update.up_to_date if update is not None else None,
"develop_version": update.development if update is not None else None,
},
"mode": os.getenv('MODE'),
"name": os.getenv('NAME'),
"info": {
"files_size_sum": self.get_file_size()
"files_size_sum": self.get_file_size(),
"files": os.listdir(f'{workdir}/data')
}
}
return jsonify(root_dict)
@self.app.route('/api/json/<path:path>')
@cross_origin()
def serve_json(path):
return send_from_directory(f'{workdir}/data', path)
@self.app.route('/charts')
@cross_origin()
def serve_index():
return send_from_directory('/src', 'chart.html')
@self.app.route('/json')
@cross_origin()
def serve_get_list_of_json():
workdir, filename = os.path.split(os.path.abspath(__file__))
return jsonify(os.listdir(f'{workdir}/data'))
@self.app.route('/json/<path:path>')
@cross_origin()
def serve_json(path):
return send_from_directory(f'{workdir}/data', path)
# --------Helpers-------
def get_file_size(self):
workdir, filename = os.path.split(os.path.abspath(__file__))
files = os.listdir(f'{workdir}/data')
sizes = 0.0
sizes = 0
for file in files:
if file.endswith('.json'):
sizes += round(int(os.path.getsize(f'{workdir}/data/{file}')), 2)
if file.endswith('.json'): sizes += os.path.getsize(f'{workdir}/data/{file}')
return sizes

View File

@ -0,0 +1,74 @@
import os
import json
import requests
DEBUG = True if os.environ.get('DEBUG') is not None else False
class State:
def __init__(self, version_int:int):
self.version_int:int = version_int
self.version_str:str = f"{str(version_int)[0]}{str(version_int)[1]}-{str(version_int)[2]}{str(version_int)[3]}-{str(version_int)[4]}{str(version_int)[5]}"
self.update_available:bool = False
self.up_to_date:bool = False
self.development:bool = False
class Release:
def __init__(self, data:dict):
self.name = data['name']
self.tag_name = data['tag_name']
self.description = data['description']
self.created_at = data['created_at']
self.released_at = data['released_at']
self.upcoming_release = data['upcoming_release']
self.version_int = int(self.tag_name.replace("-", ""))
def check_for_update():
try: version_current = int(os.getenv('VERSION').replace("-", ""))
except:
print("Error getting current version")
return
project_id = 58341398
request = f"https://gitlab.com/api/v4/projects/{project_id}/releases"
response = requests.get(url=request, timeout=1)
if not response.ok: return
releases_json = json.loads(response.text)
# Date, Object
latest = [0, None]
for release_json in releases_json:
release = Release(release_json)
if release.version_int > latest[0]:
latest[0] = release.version_int
latest[1] = release
print(repr(latest)) if DEBUG else {}
release = latest[1]
if release.version_int > version_current:
state = State(release.version_int)
state.update_available = True
return state
elif release.version_int == version_current:
state = State(release.version_int)
state.up_to_date = True
return state
else:
state = State(release.version_int)
state.development = True
return state
def print_state(state:State):
if state is None: return
print(f"Current version: {os.getenv('VERSION')}")
if state.update_available:
print(f"Update available: {state.version_str}")
if state.up_to_date:
print(f"Up to date")
if state.development:
print(f"Development Version")
print("")

View File

@ -4,8 +4,11 @@ import FindMyIP
import ipaddress
import threading
import requests
import os
DEBUG = True if os.environ.get('DEBUG') is not None else False
max_threads = 50
port = 8000
final = {}
@ -15,41 +18,37 @@ def check_port(ip, port):
# sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
socket.setdefaulttimeout(2.0) # seconds (float)
result = sock.connect_ex((ip, port))
if result == 0:
# print("Port is open")
final[ip] = "OPEN"
else:
# print("Port is closed/filtered")
final[ip] = "CLOSED"
if result == 0: final[ip] = True
else: final[ip] = False
sock.close()
except:
final[ip] = "EXCEPTION"
pass
port = 8000
def scan_for_gateways():
local_ip = FindMyIP.internal()
local_ip = local_ip.split('.')[:-1]
local_ip.append("0")
local_ip = '.'.join(local_ip)
print(f"Scan on {local_ip}/24 for port {port}")
print(f"Scan on {local_ip}/24 for port {port}") if DEBUG else {}
for ip in ipaddress.IPv4Network(f'{local_ip}/24'):
threading.Thread(target=check_port, args=[str(ip), port]).start()
# sleep(0.1)
# limit the number of threads.
while threading.active_count() > max_threads:
sleep(1)
while threading.active_count() > max_threads: sleep(1)
sorted_ips = dict(sorted(final.items(), key=lambda item: tuple(map(int, item[0].split('.')))))
gateways = []
for ip, state in sorted_ips.items():
if state == "OPEN":
if state:
try:
response = requests.get(f'http://{ip}:{port}/')
response = requests.get(f'http://{ip}:{port}/api')
# TODO: Check if the API is for real a gateway
if response.status_code == 200:
print(ip, state)
print(response.text)
print(ip, response.json()) if DEBUG else {}
gateways.append([ip, response.json()])
except:pass
return gateways

View File

@ -2,6 +2,8 @@ import os
from discovery import start_discovery
from log_data import log_to_json
from loop import start_loop
from check_update import check_for_update
from check_update import print_state
INTERVAL = 40
TIMEOUT = 20
@ -17,8 +19,13 @@ if DEBUG:
print(f"timeout: {timeout}")
print(f"DOCKER: {DOCKER}")
print(f"DEBUG: {DEBUG}")
print(f"VERSION: {os.getenv('VERSION')}")
print("")
update_state = check_for_update()
print_state(update_state)
if DOCKER:
print("Running in docker")

View File

@ -1 +1,15 @@
# TODO
import paho.mqtt.client as MQTT
topic_root = "/atc_mithermometer_gateway"
mqtt = MQTT.Client(mqtt.CallbackAPIVersion.VERSION2)
mqtt.connect("192.168.178.140", 1883, 60)
def publish_measurement(mac):
topic = f"{topic_root}/measurements/{mac}"
topic_temp = f"{topic}/temperature"
topic_humid = f"{topic}/humidity"
topic_battery = f"{topic}/battery"
mqtt.publish(topic_temp, )

View File

@ -1,14 +1,16 @@
TAG="latest"
CONTAINER="dasmoorhuhn/atc-mithermometer-gateway"
CONTAINER_NAME="ATC_MiThermometer_Gateway"
VOLUME=data
VOLUME=$(pwd)/data
BACKGROUND=""
TIME_ZONE=""
NAME=""
INTERACTIVE=false
BUILD=false
API=false
DEBUG=false
MODE=1
LOOP="0"
TIMEOUT="0"
@ -20,6 +22,8 @@ HELP="USAGE: sh run_docker.sh [OPTIONS] \n
[ -i | --interactive ] Start the container in interactive mode. That means, you can read the console output in real time and abort via STRG+C \n
[ -a | --api ] Start with the API \n
[ -v | --volume ] Set the volume, where the data from the gateway will be stored. Use relative path like /home/user/gateway/data \n
[ -n | --name ] Set a custom name for this gateway \n
[ -m2 | --mesh-gateway ] Set the mode to mesh gateway. This gateway is meant to expand the bluetooth radius. Default mode is main gateway \n
[ -tz | --timezone ] Set the timezone. Default is Europe/Berlin \n
[ -to | --timeout ] Set the timeout for the bluetooth scan. default is 20s \n
[ -h | --help ] Get this dialog \n
@ -73,6 +77,14 @@ docker_run() {
COMMAND="$COMMAND --env API=$API"
fi
if [ "$MODE" != 1 ]; then
COMMAND="$COMMAND --env MODE=$MODE"
fi
if [ "$NAME" != "" ]; then
COMMAND="$COMMAND --env NAME=$NAME"
fi
if [ "$DEBUG" = true ]; then
COMMAND="$COMMAND --env DEBUG=$DEBUG"
COMMAND="$COMMAND $CONTAINER:$TAG"
@ -114,6 +126,15 @@ while [ "$1" != "" ]; do
VOLUME=$1
shift
;;
-n | --name )
shift
NAME=$1
shift
;;
-m2 | --mesh-gateway)
MODE=2
shift
;;
-tz | --timezone )
shift
TIME_ZONE=$1