BAC0 icon indicating copy to clipboard operation
BAC0 copied to clipboard

Code after the bac0 code is not executed

Open Ferkelcode opened this issue 1 year ago • 9 comments

Hello,

I am using BAC0 together with paho MQTT, but for my code, from where the BAC0 code is positioning, it seems that any code after the bac0 code is not executed.

for example:

bacnet = BAC0.connect(ip='192.xxx.xxx.xxx/24')
bac0_measurement = bacnet.read('192.xxx.xxx.xxx analogInput 0 presentValue')

for attr in device.attributes:

    payload = json.dumps({attr.name: random.randint(0,9)}})
    logger.info("Send data to platform:" + payload)
    mqtt_client.publish(
        topic=f"/json/{service_group.apikey}/{device.device_id}/attrs",
        payload=json.dumps({attr.object_id: random.randint(0,9)}))

time.sleep(1)
entity = client.cb.get_entity(entity_id=device.device_id,
                              entity_type=device.entity_type)

logger.info("This is updated entity status after measurements are "
            "received: \n" + entity.json(indent=2))

Then all the code after BAC0.connect does not show anymore.

Is there a 'blocking' function from BAC0?

Ferkelcode avatar Sep 08 '22 11:09 Ferkelcode

try this....don't do a BAC0.connect but instead do a BAC0.lite() app and make reads, writes, releases, or what not to the BAC0.lite app directly.

I played around with mqtt a little bit and BAC0 but did not put a lot of effort into this it was just to learn mqtt in an effort... I cobbled asnycio_mqtt and BAC0 together it worked to get BACnet data present value reads of sensor values on a temporary cloud server I was experimenting with.

I could rewrite this probably way better at this point in time....but this is the mqtt app: https://github.com/bbartling/bacnet-mqtt-publisher/blob/develop/old/mqtt_publisher.py

And from bacnet_actions import BacNetWorker which should be eliminated and rewritten better: https://github.com/bbartling/bacnet-mqtt-publisher/blob/develop/old/bacnet_actions.py

I'd be interested in resurrecting and making better....what is your end goal? Just to get data onto the cloud from the building? Or are you just interacting on the building level only with other MQTT systems?

bbartling avatar Sep 08 '22 12:09 bbartling

It would be really cool to make a BAC0 MQTT gateway that is compliant by Google's Universal Device Management Interface (UDMI) schema:

  • https://github.com/faucetsdn/udmi
  • https://github.com/faucetsdn/udmi/tree/master/docs/learning

bbartling avatar Sep 08 '22 13:09 bbartling

I can squash some code to together where this works for me to publish a sensor reading to an mqtt topic:

import BAC0, time, random
import random
import time
from paho.mqtt import client as mqtt_client


bacnet = BAC0.lite()



def bens_furnace_temp():
    # test bench sensor
    read_vals = f'12345:2 analogInput 2 presentValue'
    sensor_reading = bacnet.read(read_vals)
    return round(sensor_reading,3)


print(bens_furnace_temp())

broker = 'test.mosquitto.org'
port = 1883 


# generate client ID with pub prefix randomly
client_id = "test_1"
topic_to_publish = f"bens/house/furnace/temperature"
topic_to_listen = f"mobile/publish"
topic_to_wildcard = f"testing/*"

username = ""
password = ""


def connect_mqtt():
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            client.subscribe(topic_to_listen)
            print(f"Connected to MQTT Broker on topic: {topic_to_wildcard}")
        else:
            print("Failed to connect, return code %d\n", rc)

    client = mqtt_client.Client(client_id)
    client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.connect(broker, port)
    client.on_connect = on_connect  # Define callback function for successful connection
    client.on_message = on_message  # Define callback function for receipt of a message
    return client


def publish(client):

    while True:
        
        msg = f"Sensor reading: {bens_furnace_temp()}"
        result = client.publish(topic_to_publish, msg)
        
        # result: [0, 1]
        status = result[0]
        if status == 0:
            print(f"Send {msg} to topic {topic_to_publish}")
        else:
            print(f"Failed to send message to topic {topic_to_publish}")

        time.sleep(20)

def on_message(client, userdata, msg):  # The callback for when a PUBLISH message is received from the server.
    print("Message received-> " + msg.topic + " " + str(msg.payload))



def run():
    client = connect_mqtt()
    client.loop_start()
    publish(client)


if __name__ == '__main__':
    run()

Looks like this:

Send Sensor reading: 74.98 to topic bens/house/furnace/temperature
Send Sensor reading: 74.98 to topic bens/house/furnace/temperature
Send Sensor reading: 74.98 to topic bens/house/furnace/temperature
Send Sensor reading: 74.98 to topic bens/house/furnace/temperature
Send Sensor reading: 74.96 to topic bens/house/furnace/temperature

bbartling avatar Sep 09 '22 19:09 bbartling

try this....don't do a BAC0.connect but instead do a BAC0.lite() app and make reads, writes, releases, or what not to the BAC0.lite app directly.

I played around with mqtt a little bit and BAC0 but did not put a lot of effort into this it was just to learn mqtt in an effort... I cobbled asnycio_mqtt and BAC0 together it worked to get BACnet data present value reads of sensor values on a temporary cloud server I was experimenting with.

I could rewrite this probably way better at this point in time....but this is the mqtt app: https://github.com/bbartling/bacnet-mqtt-publisher/blob/develop/old/mqtt_publisher.py

And from bacnet_actions import BacNetWorker which should be eliminated and rewritten better: https://github.com/bbartling/bacnet-mqtt-publisher/blob/develop/old/bacnet_actions.py

I'd be interested in resurrecting and making better....what is your end goal? Just to get data onto the cloud from the building? Or are you just interacting on the building level only with other MQTT systems?

Thank you very much for your reply and the advices.

My goal is to get data onto the cloud from the building

Ferkelcode avatar Sep 13 '22 12:09 Ferkelcode

For what its worth I was curious to build off these concepts on issue #346 where I almost think it would be better practice (?) using a data base to go in between MQTT scripts and BAC0 for BACnet....So maybe there is better thread of communication to keep this idea alive.

@ChristianTremblay does this git repo have a "discussions" feature?

bbartling avatar Sep 13 '22 19:09 bbartling

@Ferkelcode, here's an example with the connect() instead of lite() way of scraping BACnet data....where I said early to use lite() instead of connect I think I take that back like we should use connect instead...hopefully @ChristianTremblay can put his blessing on this as well. For example using connect incorporates BACnet polling instead of BACnet read requests that is used on the lite() method...? And Polling is better/healthier for the BACnet network right? Like we always want to cut down/minimalize read requests on networks when we can, right?

Also sort of curious on the connect method we can connect to multiple devices, right? I only know how to connect to one, is there code snip examples to connect to multiple?

@ChristianTremblay does the time.sleep in the def publish(client): will that pause BAC0 BACnet operations too on polling the BACnet network and gathering data into Pandas?

import BAC0, time, random
import random
import time
from paho.mqtt import client as mqtt_client


bacnet = BAC0.lite()

obj_list = [('file', 1),
            ('analogInput', 2),
            ('analogInput', 3)]


my_device = BAC0.device('12345:2',
                        201201,
                        bacnet,
                        object_list=obj_list,
                        segmentation_supported=False)


broker = 'test.mosquitto.org'
port = 1883 


# generate client ID with pub prefix randomly
client_id = "test_1"
topic_to_publish = f"bens/house/furnace/temperature"
topic_to_listen = f"mobile/publish"
topic_to_wildcard = f"testing/*"

username = ""
password = ""


def connect_mqtt():
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            client.subscribe(topic_to_listen)
            print(f"Connected to MQTT Broker on topic: {topic_to_wildcard}")
        else:
            print("Failed to connect, return code %d\n", rc)

    client = mqtt_client.Client(client_id)
    client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.connect(broker, port)
    client.on_connect = on_connect  # Define callback function for successful connection
    client.on_message = on_message  # Define callback function for receipt of a message
    return client


def publish(client):

    while True:

        for point in my_device.points:
            
            data = {
                point.properties.name: point.value
            }

            print(data)

            msg = f"Sensor reading is {point.properties.name}: {round(point.value,2)}"
            result = client.publish(topic_to_publish, msg)
            
            # result: [0, 1]
            status = result[0]
            if status == 0:
                print(f"Send {msg} to topic {topic_to_publish}")
            else:
                print(f"Failed to send message to topic {topic_to_publish}")

            time.sleep(60)

            

def on_message(client, userdata, msg):  # The callback for when a PUBLISH message is received from the server.
    print("Message received-> " + msg.topic + " " + str(msg.payload))



def run():
    client = connect_mqtt()
    client.loop_start()
    publish(client)


if __name__ == '__main__':
    run()

bbartling avatar Sep 14 '22 13:09 bbartling

Hello,

I am using BAC0 together with paho MQTT, but for my code, from where the BAC0 code is positioning, it seems that any code after the bac0 code is not executed.

for example:

bacnet = BAC0.connect(ip='192.xxx.xxx.xxx/24')
bac0_measurement = bacnet.read('192.xxx.xxx.xxx analogInput 0 presentValue')

for attr in device.attributes:

    payload = json.dumps({attr.name: random.randint(0,9)}})
    logger.info("Send data to platform:" + payload)
    mqtt_client.publish(
        topic=f"/json/{service_group.apikey}/{device.device_id}/attrs",
        payload=json.dumps({attr.object_id: random.randint(0,9)}))

time.sleep(1)
entity = client.cb.get_entity(entity_id=device.device_id,
                              entity_type=device.entity_type)

logger.info("This is updated entity status after measurements are "
            "received: \n" + entity.json(indent=2))

Then all the code after BAC0.connect does not show anymore.

Is there a 'blocking' function from BAC0?

BAC0 is based on bacpypes which is synchronous. Every network messages are piled up in a stack and are sent one after the other.

BAC0 is built based on threads that are allowing some parallelism. Like defining devices (BAC0.devices) will create the device with everything in it and start a polling task that will read points at a specified rate. Each read will be stores in a pandas series (device['point'].history) that can be used later.

So instead of relying on manual read, you can define a device....let the polling do its job.... and use

device['point'].lastValue and use this in your code. This will not create supplemental reading on the network. If for some reason you need the value to be fresh... device['point'].value will force a read.

Last value is just taking the last value from history pandas series.

ChristianTremblay avatar Sep 17 '22 19:09 ChristianTremblay

@bbartling

Would something like this work ?

import BAC0, time
import random
import time
from paho.mqtt import client as mqtt_client
from BAC0.tasks.RecurringTask import RecurringTask

bacnet = BAC0.lite()

# you won't use file... so don't bother with it.
obj_list = [("analogInput", 2), ("analogInput", 3)]

# poll=60 will read all the point every 60 seconds....default is 10sec...and it's too fast often
my_device = BAC0.device(
    "12345:2",
    201201,
    bacnet,
    object_list=obj_list,
    segmentation_supported=False,
    poll=60,
)


broker = "test.mosquitto.org"
port = 1883


# generate client ID with pub prefix randomly
client_id = "test_1"
topic_to_publish = f"bens/house/furnace/temperature"
topic_to_listen = f"mobile/publish"
topic_to_wildcard = f"testing/*"

username = ""
password = ""


def connect_mqtt():
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            client.subscribe(topic_to_listen)
            print(f"Connected to MQTT Broker on topic: {topic_to_wildcard}")
        else:
            print("Failed to connect, return code %d\n", rc)

    client = mqtt_client.Client(client_id)
    client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.connect(broker, port)
    client.on_connect = on_connect  # Define callback function for successful connection
    client.on_message = on_message  # Define callback function for receipt of a message
    return client


def publish(client):
    for point in my_device.points:
        msg = f"Sensor reading is {point.properties.name}: {round(point.lastValue,2)}"
        try:
            result = client.publish(topic_to_publish, msg)
            # result: [0, 1]
            status = result[0]
            if not status == 0:
                raise SendingFailed(
                    f"Failed to send message to topic {topic_to_publish}"
                )
        except SendingFailed() as e:
            print(e)
        print(
            msg
        )  # could probably be removed... on_message callback should do the job no ?


def on_message(
    client, userdata, msg
):  # The callback for when a PUBLISH message is received from the server.
    print("Message received-> " + msg.topic + " " + str(msg.payload))


def run():
    client = connect_mqtt()
    client.loop_start()
    update_task = RecurringTask(
        (
            publish,
            [
                client,
            ],
        ),
        delay=10,
    )
    update_task.start()
    while True:
        time.sleep(0.01)


if __name__ == "__main__":
    run()


class SendingFailed(Exception):
    pass

ChristianTremblay avatar Sep 17 '22 19:09 ChristianTremblay

@Ferkelcode Here is a general canvas... won't work right out but I'm pretty sure you can make it from there

import BAC0
from BAC0.tasks.RecurringTask import RecurringTask
import json
import random
import logging
import time

logger = logging.getLogger()
# if you don'T need a web server, use lite
# ip here is the IP of your network interface
bacnet = BAC0.lite(ip='192.xxx.xxx.xxx/24')


# This will read the value only 1 time...
# bac0_measurement = bacnet.read('192.xxx.xxx.xxx analogInput 0 presentValue')
# replace by a device to poll points on a regular basis using BAC0.device (here every 60 sec)
device = BAC0.device('ip_of_device', 'device_id_integer', bacnet, poll=60)

# No device created ?
def pub(mqtt_client):
    for point in device.points:
        obj_id = f"{device.properties.type}:{device.properties.address}"
        payload = json.dumps({point.properties.name: random.randint(0,9)}})
        logger.info("Send data to platform:" + payload)
        mqtt_client.publish(
            topic=f"/json/{service_group.apikey}/{device.properties.device_id}/attrs",
            payload=json.dumps({obj_id: random.randint(0,9)}))
        logger.info("This is updated entity status after measurements are "
            "received: \n" + entity.json(indent=2))

client = "SomeClientForMQTT"
entity = client.cb.get_entity(entity_id=device.device_id,
                              entity_type=device.entity_type)

publish_task = RecurringTask((pub,[client,]), delay=10)
publish_task.start()
while True:
    time.sleep(0.01)

ChristianTremblay avatar Sep 17 '22 19:09 ChristianTremblay