Add MQTT and gateway discovery
This commit is contained in:
parent
e162948cb8
commit
d7b3303e50
@ -45,6 +45,9 @@ RUN apk add --no-cache sudo bluez tzdata
|
|||||||
ENV TZ=Europe/Berlin
|
ENV TZ=Europe/Berlin
|
||||||
ENV DOCKER=true
|
ENV DOCKER=true
|
||||||
ENV API=false
|
ENV API=false
|
||||||
|
ENV NAME=ATC_MiThermometer_Gateway
|
||||||
|
ENV VERSION=24-07-03
|
||||||
|
ENV MODE=1
|
||||||
|
|
||||||
# Copy pips from the pip build stage
|
# 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
|
COPY --from=pip_build_stage /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
|
|||||||
@ -5,7 +5,6 @@ env > .env
|
|||||||
if [ "$API" = true ]; then
|
if [ "$API" = true ]; then
|
||||||
python3.12 api_endpoints.py &
|
python3.12 api_endpoints.py &
|
||||||
sleep 1
|
sleep 1
|
||||||
python3.12 find_gateways.py &
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3.12 main.py
|
python3.12 main.py
|
||||||
@ -5,3 +5,4 @@ requests
|
|||||||
flask
|
flask
|
||||||
flask_cors
|
flask_cors
|
||||||
FindMyIP
|
FindMyIP
|
||||||
|
paho-mqtt
|
||||||
@ -4,6 +4,7 @@ from flask import jsonify
|
|||||||
from flask import send_from_directory
|
from flask import send_from_directory
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_cors import cross_origin
|
from flask_cors import cross_origin
|
||||||
|
from check_update import check_for_update
|
||||||
|
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
@ -17,43 +18,44 @@ class API:
|
|||||||
self.app.config['CORS_HEADERS'] = 'Content-Type'
|
self.app.config['CORS_HEADERS'] = 'Content-Type'
|
||||||
|
|
||||||
# --------Static Routes-------
|
# --------Static Routes-------
|
||||||
@self.app.route('/')
|
@self.app.route('/api')
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
def serve_root():
|
def serve_root():
|
||||||
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
|
update = check_for_update()
|
||||||
root_dict = {
|
root_dict = {
|
||||||
"version": "24-07-03",
|
"version": {
|
||||||
"mode": 1,
|
"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": {
|
"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)
|
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')
|
@self.app.route('/charts')
|
||||||
@cross_origin()
|
@cross_origin()
|
||||||
def serve_index():
|
def serve_index():
|
||||||
return send_from_directory('/src', 'chart.html')
|
return send_from_directory('/src', 'chart.html')
|
||||||
|
|
||||||
@self.app.route('/json')
|
# --------Helpers-------
|
||||||
@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)
|
|
||||||
|
|
||||||
def get_file_size(self):
|
def get_file_size(self):
|
||||||
workdir, filename = os.path.split(os.path.abspath(__file__))
|
workdir, filename = os.path.split(os.path.abspath(__file__))
|
||||||
files = os.listdir(f'{workdir}/data')
|
files = os.listdir(f'{workdir}/data')
|
||||||
sizes = 0.0
|
sizes = 0
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.endswith('.json'):
|
if file.endswith('.json'): sizes += os.path.getsize(f'{workdir}/data/{file}')
|
||||||
sizes += round(int(os.path.getsize(f'{workdir}/data/{file}')), 2)
|
|
||||||
|
|
||||||
return sizes
|
return sizes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
74
python/src/check_update.py
Normal file
74
python/src/check_update.py
Normal 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("")
|
||||||
@ -4,8 +4,11 @@ import FindMyIP
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
|
DEBUG = True if os.environ.get('DEBUG') is not None else False
|
||||||
max_threads = 50
|
max_threads = 50
|
||||||
|
port = 8000
|
||||||
final = {}
|
final = {}
|
||||||
|
|
||||||
|
|
||||||
@ -15,41 +18,37 @@ def check_port(ip, port):
|
|||||||
# sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
|
# sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
|
||||||
socket.setdefaulttimeout(2.0) # seconds (float)
|
socket.setdefaulttimeout(2.0) # seconds (float)
|
||||||
result = sock.connect_ex((ip, port))
|
result = sock.connect_ex((ip, port))
|
||||||
if result == 0:
|
if result == 0: final[ip] = True
|
||||||
# print("Port is open")
|
else: final[ip] = False
|
||||||
final[ip] = "OPEN"
|
|
||||||
else:
|
|
||||||
# print("Port is closed/filtered")
|
|
||||||
final[ip] = "CLOSED"
|
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
except:
|
except:
|
||||||
final[ip] = "EXCEPTION"
|
pass
|
||||||
|
|
||||||
|
|
||||||
port = 8000
|
def scan_for_gateways():
|
||||||
local_ip = FindMyIP.internal()
|
local_ip = FindMyIP.internal()
|
||||||
local_ip = local_ip.split('.')[:-1]
|
local_ip = local_ip.split('.')[:-1]
|
||||||
local_ip.append("0")
|
local_ip.append("0")
|
||||||
local_ip = '.'.join(local_ip)
|
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'):
|
sorted_ips = dict(sorted(final.items(), key=lambda item: tuple(map(int, item[0].split('.')))))
|
||||||
threading.Thread(target=check_port, args=[str(ip), port]).start()
|
|
||||||
# sleep(0.1)
|
|
||||||
|
|
||||||
# limit the number of threads.
|
gateways = []
|
||||||
while threading.active_count() > max_threads:
|
for ip, state in sorted_ips.items():
|
||||||
sleep(1)
|
if state:
|
||||||
|
try:
|
||||||
sorted_ips = dict(sorted(final.items(), key=lambda item: tuple(map(int, item[0].split('.')))))
|
response = requests.get(f'http://{ip}:{port}/api')
|
||||||
|
# TODO: Check if the API is for real a gateway
|
||||||
for ip, state in sorted_ips.items():
|
if response.status_code == 200:
|
||||||
if state == "OPEN":
|
print(ip, response.json()) if DEBUG else {}
|
||||||
try:
|
gateways.append([ip, response.json()])
|
||||||
response = requests.get(f'http://{ip}:{port}/')
|
except:pass
|
||||||
if response.status_code == 200:
|
return gateways
|
||||||
print(ip, state)
|
|
||||||
print(response.text)
|
|
||||||
except:pass
|
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import os
|
|||||||
from discovery import start_discovery
|
from discovery import start_discovery
|
||||||
from log_data import log_to_json
|
from log_data import log_to_json
|
||||||
from loop import start_loop
|
from loop import start_loop
|
||||||
|
from check_update import check_for_update
|
||||||
|
from check_update import print_state
|
||||||
|
|
||||||
INTERVAL = 40
|
INTERVAL = 40
|
||||||
TIMEOUT = 20
|
TIMEOUT = 20
|
||||||
@ -17,8 +19,13 @@ if DEBUG:
|
|||||||
print(f"timeout: {timeout}")
|
print(f"timeout: {timeout}")
|
||||||
print(f"DOCKER: {DOCKER}")
|
print(f"DOCKER: {DOCKER}")
|
||||||
print(f"DEBUG: {DEBUG}")
|
print(f"DEBUG: {DEBUG}")
|
||||||
|
print(f"VERSION: {os.getenv('VERSION')}")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
update_state = check_for_update()
|
||||||
|
print_state(update_state)
|
||||||
|
|
||||||
|
|
||||||
if DOCKER:
|
if DOCKER:
|
||||||
print("Running in docker")
|
print("Running in docker")
|
||||||
|
|
||||||
|
|||||||
@ -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, )
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
TAG="latest"
|
TAG="latest"
|
||||||
CONTAINER="dasmoorhuhn/atc-mithermometer-gateway"
|
CONTAINER="dasmoorhuhn/atc-mithermometer-gateway"
|
||||||
CONTAINER_NAME="ATC_MiThermometer_Gateway"
|
CONTAINER_NAME="ATC_MiThermometer_Gateway"
|
||||||
VOLUME=data
|
VOLUME=$(pwd)/data
|
||||||
|
|
||||||
BACKGROUND=""
|
BACKGROUND=""
|
||||||
TIME_ZONE=""
|
TIME_ZONE=""
|
||||||
|
NAME=""
|
||||||
INTERACTIVE=false
|
INTERACTIVE=false
|
||||||
BUILD=false
|
BUILD=false
|
||||||
API=false
|
API=false
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
MODE=1
|
||||||
LOOP="0"
|
LOOP="0"
|
||||||
TIMEOUT="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
|
[ -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
|
[ -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
|
[ -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
|
[ -tz | --timezone ] Set the timezone. Default is Europe/Berlin \n
|
||||||
[ -to | --timeout ] Set the timeout for the bluetooth scan. default is 20s \n
|
[ -to | --timeout ] Set the timeout for the bluetooth scan. default is 20s \n
|
||||||
[ -h | --help ] Get this dialog \n
|
[ -h | --help ] Get this dialog \n
|
||||||
@ -73,6 +77,14 @@ docker_run() {
|
|||||||
COMMAND="$COMMAND --env API=$API"
|
COMMAND="$COMMAND --env API=$API"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$MODE" != 1 ]; then
|
||||||
|
COMMAND="$COMMAND --env MODE=$MODE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NAME" != "" ]; then
|
||||||
|
COMMAND="$COMMAND --env NAME=$NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$DEBUG" = true ]; then
|
if [ "$DEBUG" = true ]; then
|
||||||
COMMAND="$COMMAND --env DEBUG=$DEBUG"
|
COMMAND="$COMMAND --env DEBUG=$DEBUG"
|
||||||
COMMAND="$COMMAND $CONTAINER:$TAG"
|
COMMAND="$COMMAND $CONTAINER:$TAG"
|
||||||
@ -114,6 +126,15 @@ while [ "$1" != "" ]; do
|
|||||||
VOLUME=$1
|
VOLUME=$1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-n | --name )
|
||||||
|
shift
|
||||||
|
NAME=$1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-m2 | --mesh-gateway)
|
||||||
|
MODE=2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-tz | --timezone )
|
-tz | --timezone )
|
||||||
shift
|
shift
|
||||||
TIME_ZONE=$1
|
TIME_ZONE=$1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user