Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarification Needed: Differences Between BACpypes and BACpypes3 & Issue with elementService Attribute #58

Open
AABelkhiria opened this issue Sep 3, 2024 · 2 comments

Comments

@AABelkhiria
Copy link

Hello,

I've been working with the new BACpypes3 library and am trying to transition some of the functionality from the original BACpypes to BACpypes3. Specifically, I attempted to create a custom class inheriting from Application to perform a "Who-Is" request.

import asyncio
from bacpypes3.app import Application
from bacpypes3.apdu import WhoIsRequest
from bacpypes3.local.device import LocalDeviceObject
from bacpypes3.primitivedata import Address

class MyBACnetApplication(Application):
    def __init__(self, local_device, local_address):
        super().__init__(local_device, local_address)
    
    async def who_is(self, low_limit=None, high_limit=None, address=None):
        # Create Who-Is request
        request = WhoIsRequest()
        request.device_instance_range_low_limit = low_limit
        request.device_instance_range_high_limit = high_limit
        
        # Send the request
        await self.request(request, address)
    
    async def indication(self, apdu):
        # Handle incoming messages (e.g., I-Am responses)
        if isinstance(apdu, IAmRequest):
            print(f"Received I-Am from device {apdu.iAmDeviceIdentifier}")
        else:
            await super().indication(apdu)

async def main():
    # Local device settings
    local_device = LocalDeviceObject(
        objectIdentifier=('device', 1234),
        objectName='MyBACnetDevice',
        maxApduLengthAccepted=1024,
        segmentationSupported='noSegmentation',
        vendorIdentifier=15
    )

    # Create a BACnet application instance
    app = MyBACnetApplication(local_device, Address('0.0.0.0'))

    # Perform a Who-Is request on the local network
    await app.who_is()

    # Perform a Who-Is request on a remote network (e.g., address of a router)
    await app.who_is(address=Address('192.168.1.100'))

    # Keep the application running to process incoming I-Am responses
    while True:
        await asyncio.sleep(1)

# Run the application
if __name__ == "__main__":
    asyncio.run(main())

However, I encountered an error:

AttributeError: 'MyBACnetApplication' object has no attribute 'elementService'

In BACpypes, I was binding components together, but it seems that the equivalent mechanisms or patterns have changed or been removed in BACpypes3.

Could you please clarify:

  1. What are the major changes or differences between BACpypes and BACpypes3, particularly regarding binding.
  2. How should I approach the migration of an application that relies heavily on these bindings in BACpypes? Are there new patterns or services in BACpypes3 that replace this functionality?
  3. What is the recommended way to adapt the old BACpypes code to fit the new BACpypes3 architecture?
@bbartling
Copy link

bacpypes3 is all asyncio based. I am not sure if it does the same "binding process" as legacy bacpypes. If you pip install iffaddr you don't need to pass in an IP address (nor is there a .ini file anymore) it will automatically find it if you have iffaddr installed.

samples/discover-devices.py
https://github.com/JoelBender/BACpypes3/blob/main/samples/discover-devices.py

@JoelBender
Copy link
Owner

Re @bbartling comments about binding, BACpypes3 has a very similar architecture to legacy BACpypes, the request/indication/response/confirmation design pattern is the same (the functions are now async def ...), along with building "stacks" of components with mix-in classes implementing services.

There are substantial differences with primitive data (for example the CharacterString class extends str and the way context information is maintained is quite different), the constructed data sequences use annotations, application instances can be configured via JSON/YAML with Network Port Objects describing interfaces, and lots more.

The biggest difference for your application is that when you await app.who_is() the return value is a list of the I-Ams that have been returned that match the request and the who_is() method is built into Application via WhoIsIAmServices mix-in class so you don't need to create a custom subclass:

import asyncio
from itertools import chain

from bacpypes3.debugging import ModuleLogger
from bacpypes3.argparse import SimpleArgumentParser
from bacpypes3.pdu import Address
from bacpypes3.app import Application

# some debugging
_debug = 0
_log = ModuleLogger(globals())


async def main() -> None:
    app = None
    try:
        args = SimpleArgumentParser().parse_args()
        if _debug:
            _log.debug("args: %r", args)

        # build an application
        app = Application.from_args(args)
        if _debug:
            _log.debug("app: %r", app)

        # run the query
        task_result_list = await asyncio.gather(
            app.who_is(), app.who_is(address=Address("192.168.1.100"))
        )
        if _debug:
            _log.debug("    - task_result_list: %r", task_result_list)
        for i_am in chain.from_iterable(task_result_list):
            if _debug:
                _log.debug("    - i_am: %r", i_am)
            print(f"{i_am.iAmDeviceIdentifier[1]} at {i_am.pduSource}")

    finally:
        if app:
            app.close()


if __name__ == "__main__":
    asyncio.run(main())```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants