Skip to content

Commit

Permalink
add watchdog and fix tridium ingest issues
Browse files Browse the repository at this point in the history
  • Loading branch information
TShapinsky committed Feb 28, 2023
1 parent 2394119 commit b4208d1
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 24 deletions.
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ALFALFA_HOST=http://192.168.0.19
ALFALFA_SITE_ID=13607fa0-ad7a-11ed-a09f-a34350b09f8f
ALFALFA_HOST=https://alfalfa.nrel.gov
ALFALFA_SITE=bacnet

6 changes: 0 additions & 6 deletions BACpypes.ini

This file was deleted.

1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ RUN ln -sf /usr/bin/python3 /usr/bin/python \
&& poetry install --no-root --only main

COPY alfalfa_bacnet_bridge alfalfa_bacnet_bridge
COPY BACpypes.ini .
COPY cli_setup.py .

ENV TERM=xterm
44 changes: 31 additions & 13 deletions alfalfa_bacnet_bridge/alfalfa_bacnet_bridge.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import os
import sys
from bacpypes.app import BIPSimpleApplication
from bacpypes.core import deferred, run
from bacpypes.debugging import bacpypes_debugging, ModuleLogger
from bacpypes.local.device import LocalDeviceObject
from bacpypes.consolelogging import ConfigArgumentParser
from bacpypes.object import AnalogInputObject, register_object_type
from bacpypes.local.object import AnalogValueCmdObject
from bacpypes.task import recurring_function
from alfalfa_client.alfalfa_client import AlfalfaClient
from alfalfa_client.alfalfa_client import SiteID
from bacpypes.service.device import DeviceCommunicationControlServices
from bacpypes.service.object import ReadWritePropertyMultipleServices
from bacpypes.primitivedata import CharacterString
from bacpypes.basetypes import DeviceStatus

_debug = 0
_log = ModuleLogger(globals())
Expand All @@ -32,26 +33,38 @@ def __init__(self, sim_value, **kwargs):

def ReadProperty(self, propid, arrayIndex=None):
if propid == "presentValue":
print(super().ReadProperty(propid, arrayIndex))
print(super().ReadProperty(propid, arrayIndex).__class__)
return self._sim_value
return super().ReadProperty(propid, arrayIndex)

class AlfalfaBACnetBridge():

def __init__(self, host, site_id: SiteID) -> None:
self.client = AlfalfaClient(host)
self.site_id = site_id

parser = ConfigArgumentParser(description=__doc__)
self.site_id = site_id

args = parser.parse_args()
self.device = LocalDeviceObject(
objectName="AlfalfaProxy",
objectIdentifier=int(599),
maxApduLengthAccepted=int(1024),
segmentationSupported="segmentedBoth",
vendorIdentifier=555,
vendorName=CharacterString("NREL"),
modelName=CharacterString("Alfalfa BACnet Bridge"),
systemStatus=DeviceStatus(1),
description=CharacterString("BACpypes (Python) based tool for exposing alfalfa models to real world BAS systems via BACnet"),
firmwareRevision="0.0.0",
applicationSoftwareVersion="0.0.0",
protocolVersion=1,
protocolRevision=0)

self.device = LocalDeviceObject(ini=args.ini)
self.application = AlfalfaBACnetApplication(self.device, "0.0.0.0")

self.points = {}

def setup_points(self):

inputs = self.client.get_inputs(self.site_id)
outputs = self.client.get_outputs(self.site_id)

Expand All @@ -60,26 +73,31 @@ def setup_points(self):
for input in inputs:
if input in outputs:
self.points[input] = LocalAnalogValueObject(objectName=input, objectIdentifier=("analogValue", index), sim_value=outputs[input])
print(f"Creating BIDIRECTIONAL point: '{input}'")
else:
self.points[input] = AnalogValueCmdObject(objectName=input, objectIdentifier=("analogValue", index))
print(f"Creating INPUT point: '{input}'")
index += 1

for output, value in outputs.items():
if output in self.points:
continue
self.points[output] = AnalogInputObject(objectName=output, objectIdentifier=("analogInput", index), presentValue=value)
self.points[output] = AnalogInputObject(objectName=output, objectIdentifier=("analogInput", index), presentValue=value)
print(f"Creating OUTPUT point: '{output}'")
index += 1

for point in self.points.values():
self.application.add_object(point)

# self.application.add_object(AnalogInputObject(objectName="TEST", objectIdentifier=("analogInput", 1), presentValue=17))


def run(self):
@recurring_function(1000)
@bacpypes_debugging
def main_loop():
inputs = self.client.get_inputs(self.site_id)
outputs = self.client.get_outputs(site_id)
outputs = self.client.get_outputs(self.site_id)


set_inputs = {}
Expand All @@ -94,14 +112,14 @@ def main_loop():
if value_type is not None:
set_inputs[point] = current_value
if len(set_inputs) > 0:
self.client.set_inputs(site_id, set_inputs)
self.client.set_inputs(self.site_id, set_inputs)

deferred(main_loop)
run()

if __name__ == "__main__":
site_id = os.getenv('ALFALFA_SITE_ID')
host = os.getenv('ALFALFA_HOST')
site_id = sys.argv[2]
host = sys.argv[1]
bridge = AlfalfaBACnetBridge(host, site_id)
bridge.setup_points()
bridge.run()
82 changes: 82 additions & 0 deletions alfalfa_bacnet_bridge/alfalfa_watchdog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from logging import getLogger
import logging
import os
from subprocess import Popen, PIPE, STDOUT
import sys
from alfalfa_client import AlfalfaClient
from requests import HTTPError
from uuid import UUID
from asyncio import run, create_task
import asyncio

def get_site_id(client: AlfalfaClient, alfalfa_site: str):
try:
return client.get_alias(alfalfa_site)
except HTTPError:
if is_valid_uuid(alfalfa_site):
return alfalfa_site
else:
return None

def is_valid_uuid(uuid_str: str, version="4"):
try:
uuid_obj = UUID(uuid_str, version=version)
except ValueError:
return False
return str(uuid_obj) == uuid_str

def is_process_alive(process: Popen):
if process:
process.poll()
return process.returncode == None
return False


async def main_loop(host: str, alfalfa_site: str, command: str):

logger = getLogger("ALFALFA WATCHDOG")
logger.setLevel(logging.DEBUG)

ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

logger.addHandler(ch)

client = AlfalfaClient(host)
old_site_id = None
child_process:Popen = None


while True:
site_id = get_site_id(client, alfalfa_site)

if site_id != None and (site_id != old_site_id or not is_process_alive(child_process)):
logger.info(f"Found new site with ID: '{site_id}'")
status = client.status(site_id)
logger.info(f"Site status is: '{status}'")
if status == "running":
if is_process_alive(child_process):
logger.info(f"Killing old child process: '{child_process.pid}'")
child_process.kill()
elif child_process != None:
logger.info(f"Process '{child_process.pid}' died, restarting process")
child_process = Popen(["python", command, host, site_id])
logger.info(f"Spawned new child process: '{child_process.pid}'")
old_site_id = site_id
elif site_id == None:
logger.info(f"No site found with identifier: '{alfalfa_site}'")

await asyncio.sleep(5)

if __name__ == "__main__":
alfalfa_site = os.getenv('ALFALFA_SITE')
host = os.getenv('ALFALFA_HOST')
command = sys.argv[1]
run(main_loop(host, alfalfa_site, command))

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ packages = [{include = "alfalfa_bacnet_bridge"}]
[tool.poetry.dependencies]
python = ">=3.8, <3.11"
bacpypes = "^0.18.6"
alfalfa-client = { git = "https://github.com/nrel/alfalfa-client.git", branch = "develop" }
alfalfa-client = { git = "https://github.com/nrel/alfalfa-client.git", branch = "alias_support" }
importlib-metadata = "^6.0.0"
bac0 = "^22.9.21"

Expand Down
2 changes: 1 addition & 1 deletion start_device.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
docker stop device
docker rm device
docker build -t alfalfa-bacnet-bridge .
docker run -it --env-file=.env -p 47808:47808/udp --name="device" -h device alfalfa-bacnet-bridge poetry run python alfalfa_bacnet_bridge/alfalfa_bacnet_bridge.py
docker run -it --env-file=.env -p 47808:47808/udp --name="device" -h device alfalfa-bacnet-bridge poetry run python alfalfa_bacnet_bridge/alfalfa_watchdog.py alfalfa_bacnet_bridge/alfalfa_bacnet_bridge.py

0 comments on commit b4208d1

Please sign in to comment.