Skip to content

Commit

Permalink
Local points creation mostly works now. Good news.
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristianTremblay committed Jan 26, 2024
1 parent 798854e commit c9b058e
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 286 deletions.
114 changes: 6 additions & 108 deletions BAC0/core/app/asyncApp.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,8 @@
import asyncio
import json
import os
import sys
from asyncio import AbstractEventLoop, Future
from threading import Thread
from typing import Coroutine

from bacpypes3.apdu import ErrorRejectAbortNack
from bacpypes3.app import Application
from bacpypes3.basetypes import (
BDTEntry,
DeviceStatus,
HostNPort,
IPMode,
IPv4OctetString,
NetworkType,
ProtocolLevel,
Segmentation,
ServicesSupported,
)
from bacpypes3.comm import bind
from bacpypes3.constructeddata import AnyAtomic
from bacpypes3.ipv4 import IPv4DatagramServer
from bacpypes3.ipv4.app import BBMDApplication, ForeignApplication, NormalApplication

# for BVLL services
from bacpypes3.ipv4.bvll import Result as IPv4BVLLResult
from bacpypes3.ipv4.link import BBMDLinkLayer, ForeignLinkLayer, NormalLinkLayer
from bacpypes3.ipv4.service import (
BIPBBMD,
BIPForeign,
BIPNormal,
BVLLServiceAccessPoint,
BVLLServiceElement,
)
from bacpypes3.pdu import Address, IPv4Address
from bacpypes3.primitivedata import ObjectIdentifier, ObjectType
from bacpypes3.vendor import get_vendor_info

from BAC0.core.functions.GetIPAddr import HostIP
from bacpypes3.basetypes import BDTEntry, HostNPort

from ...core.utils.notes import note_and_log

Expand All @@ -48,15 +12,15 @@ class BAC0Application:
"""
Use bacpypes3 helpers to generate a BACnet application following
the arguments given to BAC0.
Everythign rely on the creation of the device object and the
network port object. The BACnet mode in the network port object
Everythign rely on the creation of the device object and the
network port object. The BACnet mode in the network port object
will also create the correct version of the link layer (normal, foreign, bbmd)
I chose to keep the JSON file option over the arguments only and this way,
I chose to keep the JSON file option over the arguments only and this way,
I do not close the door to applications that could be entirely defined
using a JSON file, instead of being dynamic-only.
This is why I read a JSON file, update it with information from BAC0
This is why I read a JSON file, update it with information from BAC0
command line, and use bacpypes3 from_json to generate the app.
A very important thing is to use the bacpypes3.local.objects version
Expand All @@ -72,81 +36,15 @@ class BAC0Application:

def __init__(self, cfg, addr, json_file=None):
self._cfg = cfg # backup what we wanted

self.cfg = self.update_config(cfg, json_file)
self.localIPAddr = addr
self.bdt = self._cfg["BAC0"]["bdt"]
self.device_cfg, self.networkport_cfg = self.cfg["application"]

# self.foreign_bbmd()
self._log.info(f"Configuration sent to build application : {self.cfg}")
self.app = Application.from_json(self.cfg["application"])

def bind_interface(self, addr):
self.app.link_layers = {}

self._log.info(f"Bind interface {addr} | {addr.netmask} | {addr.addrPort}")
np = self.app.get_object_name("NetworkPort-1")
link_address = np.address
if self.get_bacnet_ip_mode() == IPMode.foreign:
self.add_foreign_device_host(self._cfg["BAC0"]["bbmdAddress"])
link_layer = ForeignLinkLayer(link_address)
# start the registration process
self.app.link_layers[np.objectIdentifier] = link_layer
link_layer.register(np.fdBBMDAddress.address, np.fdSubscriptionLifetime)
# let the NSAP know about this link layer
self.app.nsap.bind(link_layer, address=link_address)
elif self.get_bacnet_ip_mode() == IPMode.bbmd:
link_layer = BBMDLinkLayer(link_address)
self.app.link_layers[np.objectIdentifier] = link_layer
for bdt_entry in np.bbmdBroadcastDistributionTable:
link_layer.add_peer(bdt_entry.address)
# let the NSAP know about this link layer
self.app.nsap.bind(link_layer, address=link_address)
else:
link_layer = NormalLinkLayer(link_address)
# let the NSAP know about this link layer
if np.networkNumber == 0:
self.app.nsap.bind(link_layer, address=link_address)
else:
self.app.nsap.bind(
link_layer, net=np.networkNumber, address=link_address
)

"""
# pick out the BVLL service access point from the local adapter
local_adapter = self.app.nsap.local_adapter
assert local_adapter
bvll_sap = local_adapter.clientPeer
# only IPv4 for now
if isinstance(bvll_sap, BVLLServiceAccessPoint):
# create a BVLL application service element
bvll_ase = BVLLServiceElement()
bind(bvll_ase, bvll_sap)
"""

def foreign_bbmd(self):
# maybe this is a foreign device
np = self.cfg["application"][1]
if self._cfg["BAC0"]["bbmdAddress"] is not None:
self._log.info("This application will act as a Foreign Device")
np["bacnet-ip-mode"] = "foreign"
_addr, _port = self._cfg["BAC0"]["bbmdAddress"].split(":")
hnp = HostNPort(self._cfg["BAC0"]["bbmdAddress"])
# np["fd-bbmd-address"] = {'host': {'ip-address': str(hnp.host.ipAddress)}, 'port': hnp.port}
np["fd-subscription-lifetime"] = self._cfg["BAC0"]["ttl"]

# maybe this is a BBMD
if self._cfg["BAC0"]["bdt"] is not None:
self._log.info("This application will act as a BBMD")
np["bacnet-ip-mode"] = "bbmd"
np["bbmd-accept-fd-registration"] = "true"
np["bbmd-foreign-device-table"] = []

def add_foreign_device_host(self, host):
"TODO : Should be able to add to the existing list..."
np = self.app.get_object_name("NetworkPort-1")
hnp = HostNPort(host)
np.fdBBMDAddress = hnp
Expand Down
91 changes: 59 additions & 32 deletions BAC0/core/devices/local/decorator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from functools import wraps

from bacpypes3.basetypes import EngineeringUnits
from bacpypes3.local.cmd import Commandable
from bacpypes3.basetypes import EngineeringUnits, PriorityValue
from bacpypes3.local.cmd import Commandable, PriorityArray
from bacpypes3.local.oos import OutOfService
from bacpypes3.object import TrendLogObject
from bacpypes3.object import TrendLogObject, Object as _Object
from bacpypes3.primitivedata import CharacterString

_SHOULD_BE_COMMANDABLE = ["relinquishDefault", "outOfService", "lowLimit", "highLimit"]
Expand Down Expand Up @@ -62,7 +62,7 @@ def datepattern(instance, objectName, presentValue, description):
"""


def _allowed_prop(obj):
"""def _allowed_prop(obj):
allowed_prop = {}
# print(obj.propertyList)
for each in obj.propertyList:
Expand All @@ -73,17 +73,49 @@ def _allowed_prop(obj):
allowed_prop[each.identifier] = each.get_datatype()
except AttributeError:
pass
return allowed_prop
return allowed_prop"""


def _mutable(property_name, force_mutable=False):
"""def _mutable(property_name, force_mutable=False):
if property_name in _SHOULD_BE_COMMANDABLE and not force_mutable:
mutable = True
elif force_mutable:
mutable = force_mutable
else:
mutable = False
return mutable
return mutable"""

_required_binary_input = (
"presentValue",
"statusFlags",
"eventState",
"outOfService",
"polarity",
)

_required_binary_output = (
"presentValue",
"statusFlags",
"eventState",
"outOfService",
"polarity",
"priorityArray",
"relinquishDefault",
"currentCommandPriority",
)

_required_analog_output = (
"presentValue",
"statusFlags",
"eventState",
"outOfService",
"polarity",
"priorityArray",
"relinquishDefault",
"currentCommandPriority",
)

_required_analog_value = ("priorityArray",)


def make_commandable():
Expand All @@ -94,19 +126,20 @@ def wrapper(*args, **kwargs):
obj = func(*args, **kwargs)
else:
obj = func
# allowed_prop = _allowed_prop(obj)
_type = obj.get_property_type("presentValue")
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__ + "Cmd"
new_type = type(base_cls_name, (base_cls, Commandable), {})
new_type.__name__ = base_cls_name
# register_object_type(new_type, vendor_id=842)
new_type = type(
base_cls_name,
(Commandable, base_cls),
{"_required": "priorityArray"},
)
objectType, instance, objectName, presentValue, description = args
new_object = new_type(
objectIdentifier=(base_cls.objectType, instance),
objectName="{}".format(objectName),
objectName=f"{objectName}",
presentValue=presentValue,
description=CharacterString("{}".format(description)),
description=CharacterString(f"{description}"),
)
return new_object

Expand All @@ -124,16 +157,18 @@ def wrapper(*args, **kwargs):
else:
obj = func
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__ + "Cmd"
new_type = type(base_cls_name, (base_cls, OutOfService), {})
new_type.__name__ = base_cls_name
# register_object_type(new_type, vendor_id=842)
base_cls_name = obj.__class__.__name__ + "OOS"
new_type = type(
base_cls_name,
(OutOfService, base_cls),
{},
)
objectType, instance, objectName, presentValue, description = args
new_object = new_type(
objectIdentifier=(base_cls.objectType, instance),
objectName="{}".format(objectName),
objectName=f"{objectName}",
presentValue=presentValue,
description=CharacterString("{}".format(description)),
description=CharacterString(f"{description}"),
)
return new_object

Expand All @@ -156,7 +191,7 @@ def wrapper(*args, **kwargs):
instance, objectName, presentValue, description = args
new_object = new_type(
objectIdentifier=(base_cls.objectType, instance),
objectName="{}".format(objectName),
objectName=f"{objectName}",
presentValue=presentValue,
description=CharacterString("{}".format(description)),
)
Expand Down Expand Up @@ -188,13 +223,6 @@ def wrapper(*args, **kwargs):
obj.__setattr__("units", new_prop)
else:
try:
# mutable = _mutable(property_name)
# new_prop = Property(
# property_name,
# allowed_prop[property_name],
# default=value,
# mutable=mutable,
# )
property_type = obj.get_property_type(property_name)
print(
f"Adding {property_name} of type {property_type} with value {value} to {obj}"
Expand All @@ -204,7 +232,6 @@ def wrapper(*args, **kwargs):
raise ValueError(
f"Invalid property ({property_name}) for object | {error}"
)
# obj.add_property(new_prop)
return obj

return wrapper
Expand All @@ -216,15 +243,15 @@ def create(object_type, instance, objectName, value, description):
if object_type is TrendLogObject:
new_object = object_type(
objectIdentifier=(object_type.objectType, instance),
objectName="{}".format(objectName),
objectName=f"{objectName}",
logBuffer=value,
description=CharacterString("{}".format(description)),
description=CharacterString("{description}"),
)
else:
new_object = object_type(
objectIdentifier=(object_type.objectType, instance),
objectName="{}".format(objectName),
objectName=f"{objectName}",
presentValue=value,
description=CharacterString("{}".format(description)),
description=CharacterString(f"{description}"),
)
return new_object
Loading

0 comments on commit c9b058e

Please sign in to comment.