JoelBender / bacpypes

BACpypes provides a BACnet application layer and network layer written in Python for daemons, scripting, and graphical interfaces.
MIT License
302 stars 129 forks source link

Implementing "WhoIsIAm" and "WhoHasIHave" services together in a single application #467

Open SRikinR opened 2 years ago

SRikinR commented 2 years ago

Hey @JoelBender, Can you please guide us on how to implement "WhoIsIAm" and "WhoHasIHave" services together in a single application?

We have implemented "WhoIsIAm" services:

  1. Made a VLAN device object (LocalDevieObject).

vlan_device = LocalDeviceObject( objectName = 'Sensor ' objectIdentifier = 'Device ' maxApduLengthAccepted=1024,
segmentationSupported='noSegmentation',
vendorIdentifier=, )

  1. Made an application and added it to the network.

vlan_app = VLANApplication(vlan_device, vlan_address)

{vlan_device = LocalDeviceObject(), vlan_address = Address(i); where i is the no. of time the loop is being called}

Below is how we have created the VLANApplication Class:
class VLANApplication(                               
Application,                                     
WhoIsIAmServices,                                                            
ReadWritePropertyServices,                       
):

vlan.add_node(vlan_app.vlan_node)

FYI: After giving the "IAm" response, we add the sensor's (end devices) properties in the VLAN application using "vlan_app.add_object(ravo)", which will get us all the available objectName and objectIdentifier in the end device(sensor). And, we have also confirmed its working using the YABE windows application

So, as per our understanding, We think that for implementing "WhoHasIHave" services we need do the following things:

  1. Once we get the end device's "objectName and objectIdentifier" we will create another LocalDeviceObject(which will have the objectName and objectIdentifier present in the end_device).

For Ex: vlan_device2 = LocalDeviceObject( objectName = ''analogInput" objectIdentifier = 1 maxApduLengthAccepted=1024,
segmentationSupported='noSegmentation',
vendorIdentifier=, )

  1. Then After, we will add this newly created LocalDeviceObject to the already created application and then add it to the network.

vlan_app = VLANApplication(vlan_device2, vlan_address) vlan.add_node(vlan_app.vlan_node)

If the above approach is incorrect, please guild us further. Else if the above approach is correct, we have some confusion regarding that, which is described below:

i. What should we pass as an address while adding this vlan_device2 into the application,

vlan_app = VLANApplication(vlan_device2, vlan_address)

ii. Let's say we have 3 types of objects in the device (analog input 1, analog input 2, binary input 1). So, firstly, we will create LocalDeviceObject for these 3 object types vlan_device2, vlan_device3, and vlan_device4 respectively. then after, for each and every object type do we need to write the following line of code? Or, is there any other way to do it all at a time?

_vlan_app = VLANApplication(vlan_device2, vlanaddress) _vlan_app = VLANApplication(vlan_device3, vlanaddress) _vlan_app = VLANApplication(vlan_device4, vlanaddress)

iii. How to cross-verify it with the help of YABE and Wireshark. We are able to capture "WhoIs" & "IAm" responses through Wireshark. but are unaware of the "WhoHas" & "IHave" responses. We have also tried running the below sample program but are unable to capture any "who-has" or "i-have" responses:

    python3 bacpypes-master/samples/HandsOnLab/Sample3_WhoHasIHaveApplication.py --ini BACpypes~.ini --debug bacpypes.udp

We Hope, you have understood our issue, please look into this, looking forward to your response. In case of any confusion please let us know!

JoelBender commented 2 years ago

The simplest way to get started is to clone the WhoIsIAmVLAN.py sample application and update the parent class list:

class VLANApplication(
    ApplicationIOController,
    WhoIsIAmServices,
    ReadWritePropertyServices,
    ):

to include the Who-Has and I-Have services by mixing in the WhoHasIHaveServices class:

class VLANApplication(
    ApplicationIOController,
    WhoIsIAmServices,
    WhoHasIHaveServices,
    ReadWritePropertyServices,
    ):

From YABE you should be able to generate a global broadcast Who-Has request for the VLAN device object (the identifier or name that matches the parameters to the LocalDeviceObject when it was created).

Now to extend the VLAN to include more devices that also have objects, follow the IP2VLANRoute.py sample application that has a loop that creates an application and adds a "random analog value" object to each one. You should then be able to send out a Who-Has for analog value object instance 1 and have all of the VLAN devices respond.

SRikinR commented 2 years ago

We have already done something similar to this, please look into it and help us further.

Here we have described how we have created the Application class & LocalDeviceObject:

1) Make the application

vlan_app = VLANApplication(vlan_device, vlan_address)
> vlan_device = \                                                           
          LocalDeviceObject(                                                                            
              objectName= "ABCD" + str(device_instance), #name update    
              objectIdentifier=('device', device_instance),                            
              maxApduLengthAccepted=1024,                                   
              segmentationSupported='noSegmentation',                       
              vendorIdentifier= <vendor_id>,                                         
              )         

 > vlan_address = Address(2)        

2) Application Class

class VLANApplication(                                                          
    Application,                                                                
    WhoIsIAmServices,                                                           
    WhoHasIHaveServices,                                                        
    ReadWritePropertyServices,                                                        
    ):                                                                          

    def __init__(self, vlan_device, vlan_address, aseID=None):                  
        if _debug: VLANApplication._debug("__init__ %r %r aseID=%r", vlan_device, vlan_address, aseID)
        global args                                                             

        # normal initialization                                                 
        Application.__init__(self, vlan_device, aseID=aseID)                    
        # optional read property multiple                                       
        #if args.rpm:                                                           
        #    self.add_capability(ReadWritePropertyMultipleServices)             

        # include a application decoder                                         
        self.asap = ApplicationServiceAccessPoint()                             

        # pass the device object to the state machine access point so it        
        # can know if it should support segmentation                            
        self.smap = StateMachineAccessPoint(vlan_device)                        

        # the segmentation state machines need access to the same device        
        # information cache as the application                                  
        self.smap.deviceInfoCache = self.deviceInfoCache                        

        # a network service access point will be needed                         
        self.nsap = NetworkServiceAccessPoint()                                 

        # give the NSAP a generic network layer service element                 
        self.nse = NetworkServiceElement()                                      
        bind(self.nse, self.nsap)                                               

        # bind the top layers                                                   
        bind(self, self.asap, self.smap, self.nsap)                             

        # create a vlan node at the assigned address                            
        self.vlan_node = Node(vlan_address)                                     

        # bind the stack to the node, no network number, no addresss            
        self.nsap.bind(self.vlan_node)                                          

    def request(self, apdu):                                                    
        if _debug: VLANApplication._debug("[%s]request %r", self.vlan_node.address, apdu)
        Application.request(self, apdu)                                         

    def indication(self, apdu):                                                 
        if _debug: VLANApplication._debug("[%s]indication %r", self.vlan_node.address, apdu)
        Application.indication(self, apdu)                                      

    def response(self, apdu):                                                   
        if _debug: VLANApplication._debug("[%s]response %r", self.vlan_node.address, apdu)
        Application.response(self, apdu)                                        

    def confirmation(self, apdu):                                               
        if _debug: VLANApplication._debug("[%s]confirmation %r", self.vlan_node.address, apdu)
        Application.confirmation(self, apdu)                                    

and also, I don't know why but, we are still not able to find how to generate global broadcast Who-Has request in YABE, we are able to send "who-is" but have not find anything with regards to "who-has". I think if we are doing right in the code, we don't have anything to verify that because we aren't capturing I-have response in the first place and that relies on who-has.

Furthermore, I am concerned about the details to be added in the LocalDeviceObject, because for "WhoHasIHave" we need the deviceIdentifier, objectIdentifier and/or objectName. for ex:

vlan_device = LocalDeviceObject
(
objectIdentifier = ('device', device_instance),    #basically a device id
objectIdentifier = ('analogInput', 1),                   #object id
objectName= "Liters of water",                          #object name 
)

but in this case, I am not able to understand how to create the proper LocalDeviceObject, as we have same variable "objectIdentifier" for device id and object id. 

Also, we noticed that later at the time of making an object, we assign the objectIdentifier and objectName as demanded, and add it to the device. So does the Who-Has gets this details for there?! 

currently in use LocalDeviceObject is as shown in step1. are we in need to modify it or its fine?!

Hope you have understood my concern, you might be aware of the correct method so, please provide your valuable suggestion.

SRikinR commented 2 years ago

@JoelBender Can you please help me with this Issue ticket? Waiting for your response.

JoelBender commented 2 years ago

You've mixed in the WhoHasIHaveServices into your application, so it will respond to a Who-Has Request via the do_WhoHasRequest() method, and incoming I-Have messages that are in response to a Who-Has that you have initiated will be via the do_IHaveRequest() so you need to provide something specific to your application that can line up your requests and responses. If you can post your application someplace (like a project or gist on GitHub) I might be able to help you.