现在有一个需求就是我只关注温度的变化,当BACnet设备(Bacnet.Room.Simulator以这个为例)的温度有变化时就直接推送给我,而不是我一直在实时监控这个温度值,如何实现呢? 答案就是预订“属性值改变”,请求当BACnet设备的温度对象的温度属性值改变时,要告知我。
订阅温度变化的效果图:
继续完善上一篇的功能,后面会给出整个工程的代码。
订阅属性值变化
发送一个订阅请求SubscribeCOVRequest
,订阅某个对象的属性值变化。
uint m_next_subscription_id = 0;
/// <summary>
/// 订阅
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSubscribe_Click(object sender, EventArgs e)
{
if (treeProperty.SelectedNode == null
|| treeDevice.SelectedNode == null)
return;
BacnetAddress adr = (BacnetAddress)treeDevice.SelectedNode.Tag;
if (adr == null) return;
BacnetObjectId objectId = (BacnetObjectId)treeProperty.SelectedNode.Tag;
if (objectId == null) return;
m_next_subscription_id++;
try
{
if (RWServices.m_service.SubscribeCOVRequest(adr, objectId, m_next_subscription_id,
false, false, 0))
{
ShowText(objectId.ToString() + "订阅成功!");
}
}catch(Exception ex)
{
ShowText(objectId.ToString() + "订阅失败: " + ex.Message);
}
}
值变化的通知
在发现设备时,我们就注册值变化通知的回调事件了。
/// <summary>
/// ClientB属性值改变的通知
/// </summary>
private void Sender_OnCOVNotification(BacnetClient sender, BacnetAddress adr,
byte invokeId, uint subscriberProcessIdentifier,
BacnetObjectId initiatingDeviceIdentifier,
BacnetObjectId monitoredObjectIdentifier,
uint timeRemaining, bool needConfirm,
ICollection<BacnetPropertyValue> values,
BacnetMaxSegments maxSegments)
{
foreach (BacnetPropertyValue value in values)
{
switch ((BacnetPropertyIds)value.property.propertyIdentifier)
{
case BacnetPropertyIds.PROP_PRESENT_VALUE:
try
{
if(value.value.Count > 0)
{
ShowText("OnCOVNotification : " + value.value[0].Value);
}
}
catch { }
break;
case BacnetPropertyIds.PROP_STATUS_FLAGS:
if (value.value != null && value.value.Count > 0)
{
BacnetStatusFlags status = (BacnetStatusFlags)((BacnetBitString)value.value[0].Value).ConvertToInt();
string status_text = "";
if ((status & BacnetStatusFlags.STATUS_FLAG_FAULT) == BacnetStatusFlags.STATUS_FLAG_FAULT)
status_text += "FAULT,";
else if ((status & BacnetStatusFlags.STATUS_FLAG_IN_ALARM) == BacnetStatusFlags.STATUS_FLAG_IN_ALARM)
status_text += "ALARM,";
else if ((status & BacnetStatusFlags.STATUS_FLAG_OUT_OF_SERVICE) == BacnetStatusFlags.STATUS_FLAG_OUT_OF_SERVICE)
status_text += "OOS,";
else if ((status & BacnetStatusFlags.STATUS_FLAG_OVERRIDDEN) == BacnetStatusFlags.STATUS_FLAG_OVERRIDDEN)
status_text += "OR,";
if (status_text != "")
{
status_text = status_text.Substring(0, status_text.Length - 1);
ShowText("OnCOVNotification : " + status_text);
}
else
ShowText("OnCOVNotification : OK");
}
break;
default:
//got something else? ignore it
break;
}
}
}
对方是怎么推送值变化的
当BACnet的某个对象的某个属性值被订阅时,它自己也会在OnSubscribeCOV
回调中收到这个属性被订阅的消息,在这个回调中,它先记录下来这个订阅,并发送SimpleAckResponse
告诉订阅方:你已经订阅这个属性值变化成功了,随后马上发这个属性值过去。(这里主要是将被订阅的属性值记录下来)
接下来在属性值发生变化时,在ChangeOfValue
中将订阅属性值发生变化的数值发送,即主动推送属性值变化。
下面的这段代码是Bacnet.Room.Simulator
中源码的一部分,感兴趣的可以去看看,最好手动敲一遍这个demo。
//触发订阅
private static void OnSubscribeCOV(BacnetClient sender, BacnetAddress adr, byte invoke_id, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments max_segments)
{
lock (m_lockObject)
{
try
{
//create 创建一个订阅
Subscription sub = HandleSubscriptionRequest(sender, adr, invoke_id,
subscriberProcessIdentifier, monitoredObjectIdentifier,
(uint)BacnetPropertyIds.PROP_ALL,
cancellationRequest,
issueConfirmedNotifications, lifetime, 0);
//send confirm 发送Ack证实服务
sender.SimpleAckResponse(adr,
BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV,
invoke_id);
//also send first values 发送属性值
if (!cancellationRequest)
{
System.Threading.ThreadPool.QueueUserWorkItem((o) =>
{
IList<BacnetPropertyValue> values;
if(m_storage.ReadPropertyAll(sub.monitoredObjectIdentifier, out values))
if (!sender.Notify(adr, sub.subscriberProcessIdentifier,
m_storage.DeviceId, sub.monitoredObjectIdentifier,
(uint)sub.GetTimeRemaining(),
sub.issueConfirmedNotifications, values))
Trace.TraceError("Couldn't send notify");
}, null);
}
}
catch (Exception)
{
sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, invoke_id,
BacnetErrorClasses.ERROR_CLASS_DEVICE,
BacnetErrorCodes.ERROR_CODE_OTHER);
}
}
}
//值改变
private static void m_storage_ChangeOfValue(DeviceStorage sender, BacnetObjectId object_id, BacnetPropertyIds property_id, uint array_index, IList<BacnetValue> value)
{
System.Threading.ThreadPool.QueueUserWorkItem((o) =>
{
lock (m_lockObject)
{
//remove old leftovers 移除过期的订阅
RemoveOldSubscriptions();
//find subscription 找订阅
if (!m_subscriptions.ContainsKey(object_id)) return;
List<Subscription> subs = m_subscriptions[object_id];
//convert
List<BacnetPropertyValue> values = new List<BacnetPropertyValue>();
BacnetPropertyValue tmp = new BacnetPropertyValue();
tmp.property = new BacnetPropertyReference((uint)property_id, array_index);
tmp.value = value;
values.Add(tmp);
//send to all 遍历所有的订阅
foreach (Subscription sub in subs)
{
if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id)
{
//send notify 发送通知
if (!sub.reciever.Notify(sub.reciever_address, sub.subscriberProcessIdentifier, m_storage.DeviceId, sub.monitoredObjectIdentifier, (uint)sub.GetTimeRemaining(), sub.issueConfirmedNotifications, values))
Trace.TraceError("Couldn't send notify");
}
}
}
}, null);
}
订阅属性值变化小结
订阅:总之就是一个去订阅属性值变化,另一个接受订阅并返回ACK,
值变化: 一个主动发送属性值变化,另外一个接收到值变化的通知。
两个设备之间有来有往。