#! /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)