历史数据就是将opcua 信息模型中的某一些变量保存起来,以便Client 端程序能够读取历史数据,作各种数据处理。
Opcua 标准指出历史数据的读写,主要包括:
- 属性 Historizing 当设置为True 时,该变量支持历史数据读写
- 属性 AccessLevel 设置为HistoryReadWirte
History 机制
存储方式:
- 存储在内存
- 存储在sqlite3 数据库中
我们重点关注Sqlite3 数据库方式。
Sqlite3
SQLite是一种用C写的小巧的嵌入式数据库,它的数据库就是一个文件。SQLite 不需要一个单独的服务器进程或操作的系统,不需要配置,这意味着不需要安装或管理,所有的维护都来自于SQLite 软件本身。python自带的轻量级数据库模块-sqlite3.python-opcua 的历史数据存储就是采用了python自带的sqlite3 实现的。具体的就是HistorySQLite 模块实现,它是一个类。
实例1 数据存储
import sys
sys.path.insert(0, "..")
import time
import math
from opcua import ua, Server
from opcua.server.history_sql import HistorySQLite
if __name__ == "__main__":
# setup our server
server = Server()
server.set_endpoint("opc.tcp://127.0.0.1:48401/freeopcua/server/")
# setup our own namespace, not really necessary but should as spec
uri = "http://examples.freeopcua.github.io"
idx = server.register_namespace(uri)
# get Objects node, this is where we should put our custom stuff
objects = server.get_objects_node()
# populating our address space
myobj = objects.add_object(idx, "MyObject")
myvar = myobj.add_variable(idx, "MyVariable", ua.Variant(0, ua.VariantType.Double))
myvar.set_writable() # Set MyVariable to be writable by clients
myvar1 = myobj.add_variable(idx, "MyVariable1", ua.Variant(0, ua.VariantType.Double))
myvar1.set_writable()
# Configure server to use sqlite as history database (default is a simple memory dict)
server.iserver.history_manager.set_storage(HistorySQLite("my_datavalue_history.sql"))
# starting!
server.start()
# enable data change history for this particular node, must be called after start since it uses subscription
server.historize_node_data_change(myvar, period=None, count=100)
server.historize_node_data_change(myvar1, period=None, count=100)
try:
count = 0
while True:
time.sleep(1)
count += 0.1
myvar.set_value(math.sin(count))
myvar1.set_value(1-math.sin(count))
finally:
# close connection, remove subscriptions, etc
server.stop()
从上述的程序可见:
定义了两个变量MyVariable和MyVariable1.
配置服务器的历史存储方式
server.iserver.history_manager.set_storage(HistorySQLite("my_datavalue_history.sql"))
HistorySQLite就是前面提及的访问sqlite 的类。它的源代码是history_sql.py。我认为,如果我们向物使用其它的数据库,例如 influxDB,mongoDB,TD Engine 等,大概只要改写history_sql.py模块就可以了。
执行上述程序行,将在程序所在目录中打开my_datavalue_history.sql文件,如果每一这样的文件,程序将自动兴建一个my_datavalue_history.sql文件。
另外就是实现历史数据值改变的handle就可以了。
# enable data change history for this particular node, must be called after start since it uses subscription
server.historize_node_data_change(myvar, period=None, count=100)
server.historize_node_data_change(myvar1, period=None, count=100)
period是历史数据的存储周期,默认为7 天。操过这个时间会自动地被删除。count是存储数据的次数。
令人惊奇的是一旦设置完成,history 数据存储是完全自动地完成的。不需要额外的程序。
sqlite3 数据库的查看
my_datavalue_history.sql是一个二进制文件,在调试阶段如果要查看存储的数据,可以使用一个sqlite3 数据库浏览器程序,我使用DB Browser for SQLite。可以在其官网下载,安装。
这是它的界面截图
浏览数据
值得一提的是,数据库表的名称是变量NodeId 的名称构成的。在我的程序中
MyVariable的NodeId 是(ns=2,i=2)和(ns=2,i=3) 所以,对应表的名称为2_2 和2_3.
历史事件的存储
if __name__ == "__main__":
# setup our server
server = Server()
server.set_endpoint("opc.tcp://localhost:48410/freeopcua/server/")
# setup our own namespace, not really necessary but should as spec
uri = "http://examples.freeopcua.github.io"
idx = server.register_namespace(uri)
# get Objects node, this is where we should put our custom stuff
objects = server.get_objects_node()
# populating our address space
myobj = objects.add_object(idx, "MyObject")
# Creating a custom event: Approach 1
# The custom event object automatically will have members from its parent (BaseEventType)
etype = server.create_custom_event_type(2, 'MyFirstEvent', ua.ObjectIds.BaseEventType,
[('MyNumericProperty', ua.VariantType.Float),
('MyStringProperty', ua.VariantType.String)])
# create second event
etype2 = server.create_custom_event_type(2, 'MySecondEvent', ua.ObjectIds.BaseEventType,
[('MyOtherProperty', ua.VariantType.Float)])
# get an event generator for the myobj node which generates custom events
myevgen = server.get_event_generator(etype, myobj)
myevgen.event.Severity = 500
myevgen.event.MyStringProperty = ua.Variant("hello world")
myevgen.event.MyNumericProperty = ua.Variant(-456)
# get another event generator for the myobj node which generates different custom events
myevgen2 = server.get_event_generator(etype2, myobj)
myevgen2.event.Severity = 123
myevgen2.event.MyOtherProperty = ua.Variant(1.337)
# get an event generator for the server node which generates BaseEventType
serverevgen = server.get_event_generator()
serverevgen.event.Severity = 111
# Configure server to use sqlite as history database (default is a simple in memory dict)
server.iserver.history_manager.set_storage(HistorySQLite("my_event_history.sql"))
# starting!
server.start()
# enable history for myobj events; must be called after start since it uses subscription
server.iserver.enable_history_event(myobj, period=None)
# enable history for server events; must be called after start since it uses subscription
server_node = server.get_node(ua.ObjectIds.Server)
server.historize_node_event(server_node, period=None)
try:
count = 0
while True:
time.sleep(1)
count += 0.1
# generate events for subscribed clients and history
myevgen.trigger(message="This is MyFirstEvent " + str(count))
myevgen2.trigger(message="This is MySecondEvent " + str(count))
serverevgen.trigger(message="Server Event Message")
# read event history from sql
end_time = datetime.utcnow()
server_event_history = server_node.read_event_history(None, end_time, 0)
finally:
# close connection, remove subscriptions, etc
server.stop()
上述两个程序均能在spyder 中运行。希望能够帮到你。