194 lines
6.9 KiB
Python
Executable File
194 lines
6.9 KiB
Python
Executable File
#! /usr/bin/python3
|
|
from smb.SMBConnection import SMBConnection
|
|
from flask import Flask, jsonify, request
|
|
from threading import Thread
|
|
from time import sleep
|
|
import time
|
|
import requests
|
|
import json
|
|
import paho.mqtt.client as mqtt
|
|
|
|
# global last_indoor_data
|
|
global indoor_server_ip
|
|
global indoor_server_password
|
|
global outdoor_api_url
|
|
global outdoor_data
|
|
global location
|
|
global mqtt_enabled
|
|
global mqtt_client
|
|
global base_topic
|
|
|
|
# Load the config file "config.json"
|
|
config = json.loads(open("config.json", "r").read())
|
|
indoor_server_ip = config["indoor_server_ip"]
|
|
indoor_server_password = config["indoor_server_password"]
|
|
outdoor_api_url = config["outdoor_api_url"]
|
|
location = config["location"]
|
|
mqtt_enabled = config["mqtt_enabled"]
|
|
|
|
# Initialize Variables
|
|
outdoor_data = {}
|
|
|
|
def mqtt_reconnect():
|
|
global mqtt_client
|
|
global mqtt_server_ip
|
|
global mqtt_server_port
|
|
global mqtt_username
|
|
global mqtt_password
|
|
global mqtt_client_id
|
|
global base_topic
|
|
if not mqtt_client.is_connected():
|
|
# Connect to the MQTT server
|
|
mqtt_client.connect(mqtt_server_ip, mqtt_server_port)
|
|
# Start the MQTT client
|
|
mqtt_client.loop_start()
|
|
if mqtt_client.is_connected():
|
|
print("Connected to MQTT server!")
|
|
else:
|
|
print("Failed to connect to MQTT server!")
|
|
return False
|
|
return True
|
|
|
|
if mqtt_enabled:
|
|
mqtt_server_ip = config["mqtt_server_ip"]
|
|
mqtt_server_port = config["mqtt_server_port"]
|
|
mqtt_username = config["mqtt_username"]
|
|
mqtt_password = config["mqtt_password"]
|
|
mqtt_client_id = config["mqtt_client_id"]
|
|
base_topic = config["base_topic"]
|
|
|
|
# Create an MQTT client
|
|
mqtt_client = mqtt.Client(client_id=mqtt_client_id)
|
|
# Set the username and password
|
|
mqtt_client.username_pw_set(mqtt_username, mqtt_password)
|
|
mqtt_reconnect()
|
|
|
|
def get_outdoor_data_current() -> dict:
|
|
# Fetch the data from the AirVisual API
|
|
# Note that API call is rate limited to 5 calls per minute
|
|
# If this function is called within 1 minute of the previous call, return the cached data
|
|
|
|
# Check if the cache file exists
|
|
# If it does not exist, create a new cache file
|
|
try:
|
|
data = json.loads(open("outdoor_data_cache.txt", "r").read())
|
|
except:
|
|
default_data = {
|
|
"pm25": 0,
|
|
"pm10": 0,
|
|
"pm1": 0,
|
|
"aqi": 0,
|
|
"temperature": 0,
|
|
"humidity": 0,
|
|
"pressure": 0,
|
|
"time": 0,
|
|
"last_updated": 0 # Unix timestamp
|
|
}
|
|
open("outdoor_data_cache.txt", "w").write(json.dumps(default_data))
|
|
data = default_data
|
|
# Is the last_updated time more than 6 minute ago?
|
|
# If it is, fetch the data from the API
|
|
# If it is not, return the cached data
|
|
# Note that the cache file is a JSON object
|
|
data["last_updated"] = int(data["last_updated"])
|
|
# Remove the last_updated key
|
|
if data["last_updated"] + 60*6 < int(time.time()):
|
|
global outdoor_api_url
|
|
url = outdoor_api_url
|
|
response = requests.get(url)
|
|
try:
|
|
print("Fetching data from API!" )
|
|
print(response)
|
|
data = response.json()
|
|
# Create a dictionary of the data
|
|
data = {
|
|
"pm25": data["current"]["pm25"]["conc"],
|
|
"pm10": data["current"]["pm10"]["conc"],
|
|
"pm1": data["current"]["pm1"]["conc"],
|
|
"aqi": data["current"]["aqius"],
|
|
"temperature": data["current"]["tp"],
|
|
"humidity": data["current"]["hm"],
|
|
"pressure": data["current"]["pr"],
|
|
"time": data["current"]["ts"]
|
|
}
|
|
# Time is in 2024-01-03T16:08:32.000Z
|
|
# Convert to GMT+7 in the format YYYY-MM-DD HH:MM:SS
|
|
# First parse the time string to a datetime object
|
|
# Then format the datetime object to YYYY-MM-DD HH:MM:SS
|
|
# The time string is in UTC time, we need to convert it to GMT+7
|
|
data["time"] = time.strptime(data["time"], "%Y-%m-%dT%H:%M:%S.000Z")
|
|
data["time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.mktime(data["time"]) + 7 * 3600))
|
|
# Update the cache file
|
|
data["last_updated"] = int(time.time())
|
|
open("outdoor_data_cache.txt", "w").write(json.dumps(data))
|
|
# Remove the last_updated key
|
|
# TODO store the data in a database
|
|
return data
|
|
except Exception as e:
|
|
print(e)
|
|
# Oops, we got rate limited
|
|
# Return the cached data
|
|
print("Rate limited!")
|
|
# Remove the last_updated key
|
|
return data
|
|
else:
|
|
# Return the cached data
|
|
print("Using cached data!")
|
|
# Remove the last_updated key
|
|
return data
|
|
|
|
def merge_data(indoor_data_current: dict, outdoor_data: dict) -> dict:
|
|
# Indoor data dict's key are to be appended with "_indoor"
|
|
# Outdoor data dict's key are to be appended with "_outdoor"
|
|
# Merge the two dictionaries
|
|
merged_data = {}
|
|
for key, value in indoor_data_current.items():
|
|
merged_data[key + "_indoor"] = value
|
|
for key, value in outdoor_data.items():
|
|
merged_data[key + "_outdoor"] = value
|
|
return merged_data
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Refresh the indoor data every 30 seconds
|
|
def refresh_data():
|
|
while True:
|
|
global outdoor_data
|
|
# print("Fetching indoor data!")
|
|
# indoor_data = get_indoor_data()
|
|
# global last_indoor_data
|
|
# # last_indoor_data the last dictionary in the list
|
|
# last_indoor_data = indoor_data[-1]
|
|
|
|
# Fetch the outdoor data
|
|
print("Fetching outdoor data!")
|
|
outdoor_data = get_outdoor_data_current()
|
|
# Reconnect the MQTT client
|
|
mqtt_reconnect()
|
|
mqtt_client.publish(base_topic+"/temperature", outdoor_data["temperature"], retain=True)
|
|
mqtt_client.publish(base_topic+"/humidity", outdoor_data["humidity"], retain=True)
|
|
mqtt_client.publish(base_topic+"/pressure", outdoor_data["pressure"], retain=True)
|
|
mqtt_client.publish(base_topic+"/pm25", outdoor_data["pm25"], retain=True)
|
|
mqtt_client.publish(base_topic+"/pm10", outdoor_data["pm10"], retain=True)
|
|
mqtt_client.publish(base_topic+"/pm1", outdoor_data["pm1"], retain=True)
|
|
mqtt_client.publish(base_topic+"/aqi", outdoor_data["aqi"], retain=True)
|
|
sleep(30)
|
|
|
|
# Start the thread to refresh the data
|
|
Thread(target=refresh_data).start()
|
|
|
|
# Return All Data in the current month
|
|
@app.route("/get_data", methods=["GET"])
|
|
def get_data_route():
|
|
global location
|
|
# global last_indoor_data
|
|
# indoor_data = last_indoor_data
|
|
|
|
# Indoor data fetch is disabled
|
|
indoor_data = {}
|
|
|
|
outdoor_data = get_outdoor_data_current()
|
|
merged_data = merge_data(indoor_data, outdoor_data)
|
|
merged_data["location"] = location
|
|
return jsonify(merged_data) |