diff --git a/Dockerfile b/Dockerfile index bf958e5..7fb1781 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/python/docker_entrypoint.sh b/python/docker_entrypoint.sh index 06a4647..aeb780b 100644 --- a/python/docker_entrypoint.sh +++ b/python/docker_entrypoint.sh @@ -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 \ No newline at end of file diff --git a/python/requierements.txt b/python/requierements.txt index 17e3ae8..1a98dde 100644 --- a/python/requierements.txt +++ b/python/requierements.txt @@ -4,4 +4,5 @@ bs4 requests flask flask_cors -FindMyIP \ No newline at end of file +FindMyIP +paho-mqtt \ No newline at end of file diff --git a/python/src/api_endpoints.py b/python/src/api_endpoints.py index 0584760..79d830d 100644 --- a/python/src/api_endpoints.py +++ b/python/src/api_endpoints.py @@ -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/') + @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/') - @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 diff --git a/python/src/check_update.py b/python/src/check_update.py new file mode 100644 index 0000000..63759f3 --- /dev/null +++ b/python/src/check_update.py @@ -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("") diff --git a/python/src/find_gateways.py b/python/src/find_gateways.py index e89ec39..2271f06 100644 --- a/python/src/find_gateways.py +++ b/python/src/find_gateways.py @@ -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 -local_ip = FindMyIP.internal() -local_ip = local_ip.split('.')[:-1] -local_ip.append("0") -local_ip = '.'.join(local_ip) +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}") if DEBUG else {} -print(f"Scan on {local_ip}/24 for port {port}") + for ip in ipaddress.IPv4Network(f'{local_ip}/24'): + threading.Thread(target=check_port, args=[str(ip), port]).start() + # limit the number of threads. + while threading.active_count() > max_threads: sleep(1) -for ip in ipaddress.IPv4Network(f'{local_ip}/24'): - threading.Thread(target=check_port, args=[str(ip), port]).start() - # sleep(0.1) + sorted_ips = dict(sorted(final.items(), key=lambda item: tuple(map(int, item[0].split('.'))))) - # limit the number of threads. - while threading.active_count() > max_threads: - sleep(1) - -sorted_ips = dict(sorted(final.items(), key=lambda item: tuple(map(int, item[0].split('.'))))) - -for ip, state in sorted_ips.items(): - if state == "OPEN": - try: - response = requests.get(f'http://{ip}:{port}/') - if response.status_code == 200: - print(ip, state) - print(response.text) - except:pass + gateways = [] + for ip, state in sorted_ips.items(): + if state: + try: + 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, response.json()) if DEBUG else {} + gateways.append([ip, response.json()]) + except:pass + return gateways diff --git a/python/src/main.py b/python/src/main.py index 2fd502a..4561b4d 100644 --- a/python/src/main.py +++ b/python/src/main.py @@ -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") diff --git a/python/src/mqtt.py b/python/src/mqtt.py index 4640904..2464945 100644 --- a/python/src/mqtt.py +++ b/python/src/mqtt.py @@ -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, ) diff --git a/run_gateway.sh b/run_gateway.sh index 4e7f3ff..47f71e0 100644 --- a/run_gateway.sh +++ b/run_gateway.sh @@ -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