我想分享我在业余时间从事的小项目。这是一个简单的智能家居项目,旨在在我的公寓中记录温度和湿度,同时还可以控制加热系统。为此,我从 Shelly 中利用传感器和恒温阀,这些传感器和恒温阀具有多种优势。这些设备能够通过Wi-Fi进行通信,它们支持MQTT协议,并提供Web REST-API接口。整个系统的核心是在Linux上运行的简单Python应用程序。该应用程序从传感器中收集数据,并将其存储在PostgreSQL数据库中。此外,还有一个在Apache上运行的Web应用程序。通过Web应用程序,我可以控制和配置整个系统。
设计系统时我有两个大问题。
第一个是确定最佳传感器放置,并决定是使用电池还是直接电源为其供电。
和秒:我没有Linux,Python,Postgresql和Apache的经验:D
请注意,这不是用于配置和设置所有必要组件的分步教程。我正在提供设置的基本概述和骨骼结构。
配置Ubuntu
作为第一步,我必须配置家庭服务器。由于其简单性,我选择了Ubuntu桌面作为操作系统。设置操作系统后,我继续逐步安装必要的服务。
首先,我通过执行以下命令安装了PostgreSQL:
$ sudo apt install postgresql postgresql-contrib
要启动PostgreSQL服务,我使用了命令:
$ sudo systemctl start postgresql.service
为了确保服务在系统启动时自动启动,我将其启用了:
$ sudo systemctl enable postgresql.service
此外,我安装了PGADMIN4,以方便使用数据库。安装程序包还包括Apache2 Web服务器。我执行了以下步骤安装pgadmin4:
$ curl https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo apt-key add
$ sudo sh -c 'echo "deb https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list && apt update'
$ sudo apt install pgadmin4
创建数据库
现在,我拥有所有必要的组件来创建一个数据库,以从传感器收集数据。为了满足我的要求,我设计了以下表:
-
房间表:
该表包含公寓中的房间列表。 -
设备表:
该表拥有所有传感器的共同属性,包括名称,WiFi连接状态,IP地址,RSSI(接收信号强度指示器),MAC地址,更新的可用性... -
RoomDevice表:
该表通过存储房间ID和设备ID来建立房间和设备之间的关系,从而允许将任何设备分配到任何房间。
前进,我创建了特定于每种设备类型的表:
-
打开窗口传感器表:
该表收集诸如温度,lux(光强度),振动,电池状态,动作原因和测量数据等数据... -
温度和湿度传感器表:
该表捕获了诸如温度,湿度,电池状态,动作原因和测量日期之类的数据... -
恒温散热器阀表:
该表收集数据,例如温度,阀门位置,窗口打开状态,电池状态,时间表配置文件和测量日期...
安装蚊子
接下来,我继续配置所有雪莉设备。我更新了它们,在家庭网络上分配了固定的IP地址,并将它们配置为连接到MQTT服务器。
哦,我几乎忘记了,我还需要安装MQTT服务器。为此,我选择了蚊子经纪人。
安装蚊子:
$ sudo apt update -y && sudo apt install mosquitto mosquitto-clients -y
启动经纪人:
$ sudo systemctl start mosquitto
使经纪人能够从系统启动开始:
$ sudo systemctl enable mosquitto
此外,我需要在防火墙中打开所需的端口:
$ sudo ufw allow 8883
Python
现在是时候让我从未冒险过的编程语言Python了。
我需要一些基本元素:与PostgreSQL互动的适配器,用于通信的MQTT客户端,记录机构和JSON解析库。
我导入以下库:
import psycopg2
import paho.mqtt.client as mqtt
import logging
import json
此外,我为配置目的定义了一组常数:
# mqtt constant
CLIENT_NAME = 'Subscrible_test'
USER_NAME = 'username'
PASSWD = 'password'
HOST = 'IP'
PORT = 1883
# postgresql constant
SQL_HOST = 'localhost'
SQL_DBNAME = 'smarthome_db'
SQL_USER = 'username'
SQL_PASSWD = 'password'
SQL_PORT = 5432
现在,我将继续设置与数据库建立连接的记录器和定义过程。一个过程处理数据库连接的创建,而另一个过程检查数据库是否可访问。我每次传感器向我发送数据时都会运行该过程以验证访问。
要创建记录器,我实现了以下代码:
#create a logger
logger = logging.getLogger('mqtt_archiver')
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
handler = logging.FileHandler(/mqtt_archiver.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# Write PID to log file
pid = os.getpid()
logger.info('pid: ' + str(pid))
上面的代码设置了一个记录器并将其配置。记录器还将进程ID(PID)写入日志文件。
接下来,我定义了两个函数: sql_connect()和 check_sql_connection(),分别处理数据库连接和连接检查:
def sql_connect():
global con
try:
con = psycopg2.connect(user=SQL_USER, password=SQL_PASSWD, host=SQL_HOST, port=SQL_PORT, database=SQL_DBNAME)
logger.info('SQL: connect to' + SQL_DBNAME)
except (Exception, psycopg2.DatabaseError) as error:
logger.error('SQL: connect error to ' + SQL_DBNAME + '. ' + str(error))
if con:
con.rollback()
def check_sql_connection():
try:
if con == None:
sql_connect()
else:
cur = con.cursor()
cur.execute("SELECT 1")
except (Exception, psycopg2.DatabaseError) as error:
print(datetime.datetime.now(), ' SQL: check connection error: ', error)
logger.error('SQL: check connection error: ' + str(error))
sql_connect()
# Connect to PostgreSQL
sql_connect()
sql_connect()函数建立了与PostgreSQL数据库的连接。它记录了成功的连接或任何遇到的错误。
check_sql_connection()函数检查数据库连接是否可用。如果不存在连接,它将调用 sql_connect()函数以建立新连接。否则,它执行一个简单的SQL语句来验证连接。
最后,该代码调用 sql_connect()函数以连接到postgresql。
接下来,我为MQTT客户端定义了几个功能:
# Called when the client connects to the server.
def on_connect(client, userdata, flags, rc):
logger.info( 'MQTT: connected with result code ' + str(rc) + '; client_id: ' + str(client._client_id))
# Called when the client disconnects from the server.
def on_disconnect(client, userdata, rc):
if rc != 0:
logger.warning( 'MQTT: unexpected disconnection. ' + str(rc) + '; client_id: ' + str(client._client_id))
# Called when a message has been received on the subscribed topic.
def on_message(client, userdata, message):
check_sql_connection()
try:
topic_array = message.topic.split('/')
if len(topic_array) > 2 and topic_array[2] == 'info':
update_tbl_device(message)
if get_device(message.topic).startswith('shellyht-'):
insert_tbl_shelly_ht(message)
elif get_device(message.topic).startswith('shellytrv-'):
insert_tbl_shelly_vrt(message)
elif get_device(message.topic).startswith('shellydw2-'):
insert_tbl_shelly_dw2(message)
elif get_device(message.topic).startswith('shellyplug-s-'):
insert_tbl_shelly_plugs(message)
except (Exception) as error:
logger.error('on_message: ' + str(error))
在 on_message()函数中,我首先检查SQL连接,然后确定接收到的消息所属的Shelly设备。基于设备类型,我处理消息。函数 update_tbl_device(message)负责解析消息并填充设备表中的基本信息。同样,函数 insert_tbl_shelly_ht(message), insert_tbl_shelly_vrt(message)和 insert_tbl_shelly_dw2(message)雪莉设备。不同之处在于 insert_tbl_shelly_ht()和 insert_tbl_shelly_dw2()触发一个将HTTP获取请求发送到恒温器散热器阀的外部功能。一个发送当前的室温(对于自动阀调节很有用),而另一个则发送状态,指示窗口是否打开(阀门打开时自动关闭)。
外部功能看起来像这样:
# Send open window state
device_id = sys.argv[1]
sensor_state = sys.argv[2]
params = {'state': sensor_state}
if device_id == 'shellydw2-XXXXXX': #shellydw-room1
response = requests.get('http://192.168.88.101/window/', params)
if device_id == 'shellydw2-YYYYYY': #shellydw-room2
response = requests.get('http://192.168.88.102/window/', params)
# Send actual temperature
device_id = sys.argv[1]
tmp_val = sys.argv[2]
hum_val = sys.argv[3]
params = {'temp': tmp_val}
if device_id == 'shellyht-AAAAAA': #shellyht-room1
response = requests.get('http://192.168.88.101/ext_t/', params)
if device_id == 'shellyht-BBBBBB': #shellyht-room2
response = requests.get('http://192.168.88.102/ext_t/', params)
现在我需要设置MQTT经纪人:
try:
client = mqtt.Client(CLIENT_NAME, clean_session=False)
client.username_pw_set(USER_NAME, PASSWD)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message=on_message
client.connect(HOST, PORT)
client.subscribe("shellies/#")
client.loop_forever()
con.close()
except (Exception) as error:
logger.error('MAIN LOOP: ' + str(error))
我创建一个新客户端,并为其提供名称和密码。此外,我为处理连接,断开和接收的消息分配了必要的功能: on_connect(), on_disconnect(),and on_message() 。我建立了与MQTT经纪人的连接,并订阅了一组特定的主题。就我而言,主题由“ shellies/#” 表示。最后,我启动执行无限循环以处理传入消息。
最后,确保当服务器启动时自动启动此应用程序。为了实现这一目标,我利用Cron是Ubuntu的任务自动化工具。
网页
接下来,我为我的智能家庭项目创建了一个简单的网页。索引页面提供了所有房间的概述,并在每个房间内的设备及其最重要的值显示。单击房间可打开详细的视图,其中所有值均列出。另外,可以修改某些设备的设置,例如设置温度或更改散热器阀的套筒轮廓。
这是创建自己的DIY智能家庭系统的方法。只需几个传感器即可读取数据并处理并存储它。可以根据存储的数据来控制其他活性元素,例如恒温散热器阀。
将来,我计划通过聚光灯和LED条扩展系统,可以通过场景控制器设备或网页控制。