├── .flake8
├── .gitignore
├── .gitlab-ci.yml
├── Pipfile
├── README.md
├── bacpypes3
├── __init__.py
├── __main__.py
├── apdu.py
├── app.py
├── appservice.py
├── argparse.py
├── basetypes.py
├── cmd.py
├── comm.py
├── console.py
├── constructeddata.py
├── debugging.py
├── errors.py
├── ipv4
│ ├── __init__.py
│ ├── app.py
│ ├── bvll.py
│ ├── link.py
│ └── service.py
├── ipv6
│ ├── __init__.py
│ ├── bvll.py
│ └── service.py
├── json
│ ├── __init__.py
│ ├── __main__.py
│ └── util.py
├── lib
│ ├── __init__.py
│ └── batchread.py
├── local
│ ├── __init__.py
│ ├── analog.py
│ ├── binary.py
│ ├── cmd.py
│ ├── cov.py
│ ├── device.py
│ ├── event.py
│ ├── fault.py
│ ├── multistate.py
│ ├── networkport.py
│ ├── object.py
│ ├── oos.py
│ └── schedule.py
├── netservice.py
├── npdu.py
├── object.py
├── pdu.py
├── primitivedata.py
├── rdf
│ ├── __init__.py
│ ├── __main__.py
│ ├── core.py
│ └── util.py
├── sc
│ ├── __init__.py
│ ├── bvll.py
│ └── service.py
├── service
│ ├── __init__.py
│ ├── cov.py
│ ├── device.py
│ └── object.py
├── settings.py
├── vendor.py
└── vlan
│ ├── __init__.py
│ └── link.py
├── bdist.sh
├── check-install.py
├── check-install.sh
├── doc
├── Makefile
├── make.bat
└── source
│ ├── _figures
│ ├── template.odg
│ ├── template.png
│ ├── template.svg
│ └── template.tif
│ ├── _static
│ └── deleteme
│ ├── _templates
│ └── deleteme
│ ├── conf.py
│ ├── gettingstarted
│ ├── addresses.rst
│ ├── debugging.rst
│ ├── index.rst
│ ├── objectidentifiers.rst
│ ├── running.rst
│ ├── setup.rst
│ └── udpcommunications.rst
│ ├── glossary.rst
│ ├── index.rst
│ └── samples
│ ├── apdu-hex-decode.rst
│ ├── client-server.odg
│ ├── client-server.png
│ ├── cmd-address.rst
│ ├── cmd-debugging.rst
│ ├── cmd-samples.rst
│ ├── console-ase-sap.rst
│ ├── console-ini.rst
│ ├── console-json.rst
│ ├── console-prompt.rst
│ ├── console-yaml.rst
│ ├── console.rst
│ ├── cov-client.rst
│ ├── cov-server.rst
│ ├── custom-cache.rst
│ ├── custom-client.rst
│ ├── custom-server.rst
│ ├── custom.rst
│ ├── discover-devices.rst
│ ├── discover-objects.rst
│ ├── docker
│ └── index.rst
│ ├── index.rst
│ ├── read-batch.rst
│ ├── read-bbmd.rst
│ ├── read-property.rst
│ ├── router-json.rst
│ ├── start-here.rst
│ ├── vlan-console-1.rst
│ ├── vlan-console-2.rst
│ ├── vlan-console-3.rst
│ ├── who-has.rst
│ └── write-property.rst
├── pyproject.toml
├── pytest.ini
├── release_to_pypi.sh
├── release_to_testpypi.sh
├── samples
├── BACpypes.ini
├── BACpypes.json
├── BACpypes.yml
├── apdu-hex-decode.py
├── cmd-address.py
├── cmd-debugging.py
├── cmd-samples.py
├── console-ase-sap.py
├── console-ini.py
├── console-json.py
├── console-prompt.py
├── console-yaml.py
├── console.py
├── cov-client-shell.py
├── cov-client.py
├── cov-server.py
├── custom-client.py
├── custom-device-cache.py
├── custom-router-cache.py
├── custom-server.py
├── custom.py
├── device-object-init-1.py
├── device-object-init-2.py
├── discover-devices.py
├── discover-objects-rdf.py
├── discover-objects-rdf.ttl
├── discover-objects.py
├── docker
│ ├── README.md
│ ├── bacpypes-build.sh
│ ├── bacpypes-greetings-build.sh
│ ├── bacpypes-greetings-run.sh
│ ├── bacpypes-greetings-save.sh
│ ├── bacpypes-greetings.dockerfile
│ ├── bacpypes-greetings.py
│ ├── bacpypes-run.sh
│ ├── bacpypes-save.sh
│ ├── bacpypes.dockerfile
│ ├── requirements.txt
│ ├── who-is-build.sh
│ ├── who-is-console-build.sh
│ ├── who-is-console-run-mini.sh
│ ├── who-is-console-run.sh
│ ├── who-is-console-save.sh
│ ├── who-is-console.dockerfile
│ ├── who-is-console.py
│ ├── who-is-run.sh
│ ├── who-is-save.sh
│ ├── who-is.dockerfile
│ └── who-is.py
├── find-device-by-address.py
├── find-device-by-instance.py
├── ip-to-vlan.json
├── ip-to-vlan.py
├── ipv4-to-ipv4.json
├── ipv4-to-ipv4.py
├── link-layer
│ ├── sc-echo-client.py
│ ├── sc-echo-server.py
│ ├── udp-ipv4-echo-client-build.sh
│ ├── udp-ipv4-echo-client-run.sh
│ ├── udp-ipv4-echo-client.dockerfile
│ ├── udp-ipv4-echo-client.py
│ ├── udp-ipv4-echo-server.py
│ ├── udp-ipv6-echo-client.py
│ └── udp-ipv6-echo-server.py
├── mini-device-revisited.py
├── multiple-stacks.json
├── multiple-stacks.py
├── network-port-1.py
├── read-batch-point-list.py
├── read-batch.py
├── read-bbmd.py
├── read-property-console.py
├── read-property-multiple-console.py
├── read-property.py
├── read-range-console.py
├── router-config.json
├── router-json.py
├── rpc-server.py
├── start-here.py
├── vlan-console-1.py
├── vlan-console-2.py
├── vlan-console-3.py
├── who-has.py
├── write-property-console.py
└── write-property.py
├── sandbox
├── calendar-server.py
├── discover-ipv4-foreign-build.sh
├── discover-ipv4-foreign-run.sh
├── discover-ipv4-foreign-save.sh
├── discover-ipv4-foreign.dockerfile
├── discover-ipv4-foreign.py
├── discover-ipv4-normal.py
├── event-notification-recipient.py
├── event-server-avo1-fd.py
├── event-server-avo1-ir.py
├── event-server-avo2.py
├── event-server-avo3.py
├── event-server-bio1.py
├── event-server-bvo1.py
├── event-server-bvo2.py
├── event-server.py
├── get-ip-address.py
├── get-netmask.py
├── initialize-routing-table.py
├── link-layer
│ ├── ipv4-bbmd-server.py
│ ├── ipv4-foreign-client.py
│ ├── ipv4-normal-client.py
│ ├── ipv4-normal-server-broadcast.py
│ ├── ipv4-normal-server.py
│ ├── ipv6-bbmd-server-broadcast.py
│ ├── ipv6-bbmd-server.py
│ ├── ipv6-foreign-client.py
│ ├── ipv6-normal-client.py
│ └── ipv6-normal-server.py
├── mqtt-a-side.py
├── mqtt-b-side.py
├── net-normal-client.py
├── net-normal-server.py
├── normal-discover.py
├── octetstring-server.py
├── redis-server.py
└── write-property-multiple.py
├── setup.py
├── testpypi_install_bacpypes3.sh
└── tests
├── __init__.py
├── clocked_test.py
├── conftest.py
├── mypy.ini
├── state_machine.py
├── test_1.py
├── test__template.py
├── test_app
├── __init__.py
├── test_parse_reference.py
└── test_read_write_api.py
├── test_basetypes
├── __init__.py
└── test_datetime.py
├── test_constructed_data
├── __init__.py
├── test_any.py
├── test_array.py
├── test_choice.py
├── test_list.py
├── test_read_property_multiple.py
├── test_sequence.py
└── test_sequence_of.py
├── test_pdu
├── __init__.py
├── test_address.py
├── test_pci.py
└── test_pdu.py
├── test_primitive_data
├── __init__.py
├── test_bit_string.py
├── test_boolean.py
├── test_character_string.py
├── test_date.py
├── test_double.py
├── test_enumerated.py
├── test_integer.py
├── test_null.py
├── test_object_identifier.py
├── test_octet_string.py
├── test_real.py
├── test_tag.py
├── test_time.py
└── test_unsigned.py
├── test_utilities
├── __init__.py
└── test_state_machine.py
├── test_vlan
├── __init__.py
├── test_ipv4_network.py
├── test_ipv4_router.py
└── test_network.py
├── trapped_classes.py
└── utilities.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 88
3 | extend-ignore = E203, E501, E262, E265
4 | per-file-ignores =
5 | setup.py:E121
6 | bacpypes3/__init__.py:F401, E402
7 | bacpypes3/ipv4/__init__.py:F401, E402
8 | bacpypes3/ipv6/__init__.py:F401, E402
9 | bacpypes3/json/__init__.py:F401, E402
10 | bacpypes3/lib/__init__.py:F401, E402
11 | bacpypes3/local/__init__.py:F401, E402
12 | bacpypes3/rdf/__init__.py:F401, E402
13 | bacpypes3/sc/__init__.py:F401, E402
14 | bacpypes3/service/__init__.py:F401, E402
15 | bacpypes3/vlan/__init__.py:F401, E402
16 | tests/test_vlan/__init__.py:F401, E402
17 | tests/test_utilities/__init__.py:F401, E402
18 | tests/test_pdu/__init__.py:F401, E402
19 | tests/test_primitive_data/__init__.py:F401, E402
20 | tests/test_constructed_data/__init__.py:F401, E402
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # Editor backups
6 | *~
7 | *.bak
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | # lib/
19 | # lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | *.whl
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *,cover
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Log files
53 | *.log
54 |
55 | # History files
56 | *.history
57 |
58 | # Sphinx documentation
59 | docs/_build/
60 |
61 | # PyBuilder
62 | target/
63 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | # SERVISYS inc. Gitlab-CI Configuration file
2 | # Official language image. Look for the different tagged releases at:
3 | # https://hub.docker.com/r/library/python/tags/
4 | image: python:latest
5 |
6 | # Change pip's cache directory to be inside the project directory since we can
7 | # only cache local items.
8 | variables:
9 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
10 |
11 | # Pip's cache doesn't store the python packages
12 | # https://pip.pypa.io/en/stable/reference/pip_install/#caching
13 | #
14 | # If you want to also cache the installed packages, you have to install
15 | # them in a virtualenv and cache it as well.
16 | cache:
17 | paths:
18 | - .cache/pip
19 | - venv/
20 |
21 | before_script:
22 | - python -V # Print out python version for debugging
23 | - pip install virtualenv
24 | - pip install websockets
25 | - pip install colorama
26 | - pip install atomicwrites
27 | - pip install pytest
28 | - pip install pytest-asyncio
29 | - virtualenv venv
30 | - source venv/bin/activate
31 |
32 | test_python:
33 | script:
34 | - pip install .
35 | - pytest -v
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | verify_ssl = true
3 | name = "pypi"
4 | url = "https://pypi.python.org/simple"
5 |
6 | [packages]
7 | bacpypes3 = {editable = true, path = "."}
8 |
9 | [dev-packages]
10 | build = "*"
11 | ifaddr = "*"
12 | rdflib = "*"
13 | websockets = "*"
14 | pyyaml = "*"
15 | black = "*"
16 | flake8 = "*"
17 | mypy = "*"
18 | pytest = "*"
19 | pytest-asyncio = "*"
20 | sphinx = "*"
21 | sphinx-rtd-theme = "*"
22 | twine = "*"
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BACpypes3 - Python library for BACnet applications
2 |
3 | BACpypes provides a BACnet application layer and network layer written in
4 | Python for daemons, scripting, and graphical interfaces. This version is the
5 | next generation for Python3.8+, completely overhauled from the Python2.5+
6 | version using async/await methods and asyncio for communications.
7 |
--------------------------------------------------------------------------------
/bacpypes3/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | BACnet Python Package
3 | """
4 |
5 | #
6 | # Platform Check
7 | #
8 |
9 | import sys as _sys
10 | import warnings as _warnings
11 |
12 | _supported_platforms = ("linux", "win32", "darwin")
13 |
14 | if _sys.platform not in _supported_platforms:
15 | _warnings.warn("unsupported platform", RuntimeWarning)
16 |
17 | #
18 | # Project Metadata
19 | #
20 |
21 | __version__ = "0.0.102"
22 | __author__ = "Joel Bender"
23 | __email__ = "joel@carrickbender.com"
24 |
25 | #
26 | # Settings and Debugging
27 | #
28 |
29 | from . import settings
30 | from . import debugging
31 | from . import errors
32 |
33 | #
34 | # Communications Core Modules
35 | #
36 |
37 | from . import pdu
38 | from . import comm
39 |
40 | #
41 | # Shell
42 | #
43 |
44 | from . import argparse
45 | from . import console
46 |
47 | # from . import singleton
48 | # from . import capability
49 |
50 | #
51 | # Link Layer Modules
52 | #
53 |
54 | from . import ipv4
55 | from . import ipv6
56 | from . import vlan
57 |
58 | try:
59 | import websockets
60 | from . import sc
61 | except ImportError:
62 | pass
63 |
64 | #
65 | # Network Layer Modules
66 | #
67 |
68 | from . import npdu
69 | from . import netservice
70 |
71 | #
72 | # Application Layer Modules
73 | #
74 |
75 | from . import primitivedata
76 | from . import constructeddata
77 | from . import basetypes
78 | from . import object
79 | from . import apdu
80 |
81 | from . import app
82 | from . import appservice
83 |
84 | from . import local
85 | from . import service
86 | from . import vendor
87 |
88 | #
89 | # Library of useful functions
90 | #
91 |
92 | from . import lib
93 | from . import json
94 |
95 | try:
96 | import rdflib # type: ignore[import]
97 | from . import rdf
98 | except ImportError:
99 | pass
100 |
101 | #
102 | # Analysis
103 | #
104 |
105 | # from . import analysis
106 |
--------------------------------------------------------------------------------
/bacpypes3/json/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | BACnet/JSON
3 | """
4 |
5 | from .util import apdu_to_json, json_to_apdu, json_to_sequence, sequence_to_json
6 |
--------------------------------------------------------------------------------
/bacpypes3/json/__main__.py:
--------------------------------------------------------------------------------
1 | """
2 | Command Line Interpreter
3 | """
4 |
5 | import sys
6 |
7 | if __name__ == "__main__":
8 | print("bacpypes json cli placeholder")
9 | sys.exit(1)
10 |
--------------------------------------------------------------------------------
/bacpypes3/lib/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Library
3 | #
4 |
5 | from . import batchread
6 |
--------------------------------------------------------------------------------
/bacpypes3/local/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Local Objects
3 | #
4 |
5 | from ..basetypes import PropertyIdentifier
6 | from ..vendor import VendorInfo
7 |
8 |
9 | class LocalPropertyIdentifier(PropertyIdentifier):
10 | settings = 512
11 |
12 |
13 | local_vendor_info = VendorInfo(999, property_identifier=LocalPropertyIdentifier)
14 |
15 | from . import object
16 | from . import cmd
17 | from . import cov
18 | from . import device
19 | from . import analog
20 | from . import binary
21 | from . import event
22 | from . import fault
23 | from . import networkport
24 | from . import schedule
25 |
--------------------------------------------------------------------------------
/bacpypes3/local/multistate.py:
--------------------------------------------------------------------------------
1 | """
2 | Multistate Input, Output, and Value Object
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from typing import Callable
8 |
9 | from ..debugging import ModuleLogger, bacpypes_debugging
10 |
11 | # object module provides basic AnalogInputObject
12 | from ..object import MultiStateInputObject as _MultiStateInputObject
13 | from ..object import MultiStateOutputObject as _MultiStateOutputObject
14 | from ..object import MultiStateValueObject as _MultiStateValueObject
15 | from .cmd import Commandable
16 | from .cov import COVIncrementCriteria
17 | from .event import OutOfRangeEventAlgorithm
18 | from .fault import OutOfRangeFaultAlgorithm
19 |
20 | # local object provides dynamically generated propertyList property
21 | from .object import Object as _Object
22 |
23 | # some debugging
24 | _debug = 0
25 | _log = ModuleLogger(globals())
26 |
27 |
28 | # this is for sample applications
29 | _vendor_id = 999
30 |
31 |
32 | @bacpypes_debugging
33 | class MultiStateInputObject(_Object, _MultiStateInputObject):
34 | """
35 | A local multistate input object.
36 | """
37 |
38 | _debug: Callable[..., None]
39 | _cov_criteria = COVIncrementCriteria
40 | _required = (
41 | "presentValue",
42 | "statusFlags",
43 | "eventState",
44 | "outOfService",
45 | "numberOfStates",
46 | )
47 |
48 |
49 | @bacpypes_debugging
50 | class MultiStateOutputObject(Commandable, _Object, _MultiStateOutputObject):
51 | """
52 | A local multistate output object.
53 | """
54 |
55 | _debug: Callable[..., None]
56 | _cov_criteria = COVIncrementCriteria
57 | _required = (
58 | "presentValue",
59 | "statusFlags",
60 | "eventState",
61 | "outOfService",
62 | "numberOfStates",
63 | "priorityArray",
64 | "relinquishDefault",
65 | "currentCommandPriority",
66 | )
67 |
68 |
69 | @bacpypes_debugging
70 | class MultiStateValueObject(_Object, _MultiStateValueObject):
71 | """
72 | Vanilla Multistate Value Object
73 | """
74 |
75 | _debug: Callable[..., None]
76 | _cov_criteria = COVIncrementCriteria
77 | _required = (
78 | "presentValue",
79 | "priorityArray",
80 | "statusFlags",
81 | "eventState",
82 | "outOfService",
83 | "numberOfStates",
84 | )
85 |
--------------------------------------------------------------------------------
/bacpypes3/local/oos.py:
--------------------------------------------------------------------------------
1 | from typing import Any as _Any, Callable, Optional, Union
2 |
3 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
4 |
5 | from bacpypes3.errors import PropertyError
6 | from bacpypes3.basetypes import PropertyIdentifier
7 |
8 | # some debugging
9 | _debug = 0
10 | _log = ModuleLogger(globals())
11 |
12 |
13 | @bacpypes_debugging
14 | class OutOfService:
15 | """
16 | This mix-in class is used to make present-value readonly unless out-of-service
17 | is true.
18 | """
19 |
20 | _debug: Callable[..., None]
21 |
22 | async def write_property( # type: ignore[override]
23 | self,
24 | attr: Union[int, str],
25 | value: _Any,
26 | index: Optional[int] = None,
27 | priority: Optional[int] = None,
28 | ) -> None:
29 | if _debug:
30 | OutOfService._debug(
31 | "write_property %r %r %r %r", attr, value, index, priority
32 | )
33 | if (attr == PropertyIdentifier.presentValue) and (not self.outOfService):
34 | raise PropertyError("writeAccessDenied")
35 |
36 | # pass along
37 | await super().write_property(attr, value, index, priority)
38 |
--------------------------------------------------------------------------------
/bacpypes3/rdf/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | BACnet/RDF Python Package
3 | """
4 |
5 | #
6 | # Platform Check
7 | #
8 |
9 | import sys as _sys
10 | import warnings as _warnings
11 |
12 | _supported_platforms = ("linux", "win32", "darwin")
13 |
14 | if _sys.platform not in _supported_platforms:
15 | _warnings.warn("unsupported platform", RuntimeWarning)
16 |
17 | #
18 | #
19 | #
20 |
21 | from .core import BACnetNS, BACnetGraph
22 |
23 | from . import core
24 | from . import util
25 |
--------------------------------------------------------------------------------
/bacpypes3/rdf/__main__.py:
--------------------------------------------------------------------------------
1 | """
2 | Command Line Interpreter
3 | """
4 | import sys
5 |
6 | if __name__ == "__main__":
7 | print("bacpypes rdf cli placeholder")
8 | sys.exit(1)
9 |
--------------------------------------------------------------------------------
/bacpypes3/sc/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Secure Connect
3 | """
4 |
5 | from . import bvll
6 | from . import service
7 |
--------------------------------------------------------------------------------
/bacpypes3/service/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Services
3 | #
4 |
5 | from . import device
6 | from . import object
7 | from . import cov
8 |
--------------------------------------------------------------------------------
/bacpypes3/vlan/link.py:
--------------------------------------------------------------------------------
1 | """
2 | Link Layer Module
3 |
4 | Classes in this module extend the BVLLServiceAccessPoint subclasses with
5 | additional components necessary to send packets down to a virtual network.
6 |
7 | The clients of instances of these object (next up the stack) is a NetworkAdapter
8 | instance which references a NetworkServiceAccessPoint.
9 | """
10 |
11 | from __future__ import annotations
12 |
13 | from ..debugging import bacpypes_debugging, ModuleLogger
14 |
15 | from ..comm import bind, Client, Server
16 | from ..pdu import PDU, LocalStation
17 | from ..vlan import VirtualNode
18 |
19 | # some debugging
20 | _debug = 0
21 | _log = ModuleLogger(globals())
22 |
23 | #
24 | # VirtualLinkLayer
25 | #
26 |
27 |
28 | @bacpypes_debugging
29 | class VirtualLinkLayer(Client[PDU], Server[PDU]):
30 | """
31 | Create a link layer mini-stack ...
32 | """
33 |
34 | def __init__(
35 | self, local_address: LocalStation, network_interface_name: str, **kwargs
36 | ) -> None:
37 | if _debug:
38 | VirtualLinkLayer._debug(
39 | "__init__ %r %r %r",
40 | local_address,
41 | network_interface_name,
42 | kwargs,
43 | )
44 |
45 | # create a node
46 | self.node = VirtualNode(local_address, network_interface_name)
47 |
48 | # add it below this in the stack
49 | bind(self, self.node)
50 |
51 | async def indication(self, pdu: PDU) -> None:
52 | if _debug:
53 | VirtualLinkLayer._debug("indication %r", pdu)
54 |
55 | # continue down the stack
56 | await self.request(pdu)
57 |
58 | async def confirmation(self, pdu: PDU) -> None:
59 | if _debug:
60 | VirtualLinkLayer._debug("confirmation %r", pdu)
61 |
62 | # countinue up the stack
63 | await self.response(pdu)
64 |
65 | def close(self):
66 | if _debug:
67 | VirtualLinkLayer._debug("close")
68 |
--------------------------------------------------------------------------------
/bdist.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # remove everything in the current dist/ directory
4 | [ -d dist ] && rm -Rfv dist
5 |
6 | # start with a clean build directory
7 | [ -d build ] && rm -Rfv build
8 |
9 | # use the build package
10 | python3 -m build --no-isolation
11 |
12 | echo
13 | echo This is what was built...
14 | echo
15 | ls -1 dist/
16 | echo
17 |
18 | # copy the wheel into the docker samples
19 | cp -v dist/*.whl samples/docker/
20 |
21 | echo
22 |
--------------------------------------------------------------------------------
/check-install.py:
--------------------------------------------------------------------------------
1 | """
2 | Check to see what versions of modules are installed.
3 | """
4 | import sys
5 |
6 | try:
7 | import bacpypes3
8 |
9 | print("bacpypes3:", bacpypes3.__version__, bacpypes3.__file__)
10 | except:
11 | print("bacpypes3: not installed")
12 |
13 | try:
14 | import websockets
15 |
16 | print("websockets:", websockets.__version__, websockets.__file__)
17 | except:
18 | print("websockets: not installed")
19 |
20 | try:
21 | import ifaddr
22 |
23 | print("ifaddr:", ifaddr.__file__)
24 | except:
25 | print("ifaddr: not installed")
26 |
27 | try:
28 | import yaml
29 |
30 | print("pyyaml:", yaml.__version__, yaml.__file__)
31 | except:
32 | print("pyyaml: not installed")
33 |
34 | try:
35 | import rdflib
36 |
37 | print("rdflib:", rdflib.__version__, rdflib.__file__)
38 | except:
39 | print("rdflib: not installed")
40 |
--------------------------------------------------------------------------------
/check-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for version in 3.7 3.8 3.9 3.10;
4 | do
5 | if [ -a "`which python$version`" ]; then
6 | echo "===== $version ====="
7 | python$version check-install.py
8 | echo
9 | fi
10 | done
11 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/doc/source/_figures/template.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/_figures/template.odg
--------------------------------------------------------------------------------
/doc/source/_figures/template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/_figures/template.png
--------------------------------------------------------------------------------
/doc/source/_figures/template.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/_figures/template.tif
--------------------------------------------------------------------------------
/doc/source/_static/deleteme:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/_static/deleteme
--------------------------------------------------------------------------------
/doc/source/_templates/deleteme:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/_templates/deleteme
--------------------------------------------------------------------------------
/doc/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | project = 'BACpypes3'
10 | copyright = '2023, Joel Bender'
11 | author = 'Joel Bender'
12 |
13 | # -- General configuration ---------------------------------------------------
14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15 |
16 | extensions = []
17 |
18 | templates_path = ['_templates']
19 | exclude_patterns = []
20 |
21 |
22 |
23 | # -- Options for HTML output -------------------------------------------------
24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
25 |
26 | html_theme = 'sphinx_rtd_theme'
27 | html_static_path = ['_static']
28 |
--------------------------------------------------------------------------------
/doc/source/gettingstarted/addresses.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes3 Addresses
2 |
3 | BACpypes3 Addresses
4 | ===================
5 |
6 | BACpypes3 addresses are used to communicate between BACnet devices directly
7 | (one local station to another) or through a router (a local station through one
8 | or more routers to another local station).
9 |
10 | There are also special addresses to send a message to all of the stations on
11 | the local network (called a "local broadcast"), all of the stations on a
12 | specific network through one of more routers (called a "remote broadcast") or
13 | to all of the stations on all of the networks (called a "global broadcast").
14 |
15 | .. list-table:: Address Patterns
16 | :widths: 25 25 50
17 | :header-rows: 1
18 |
19 | * - Type
20 | - Example
21 | - Context
22 | * - Local Station
23 | - 12
24 | - ARCNET, MS/TP
25 | * -
26 | - 192.168.0.10
27 | - IPv4, standard port 47808
28 | * -
29 | - 192.168.0.11/24
30 | - IPv4 CIDR, standard port 47808
31 | * -
32 | - 192.168.0.12/255.255.255.0
33 | - IPv4 subnet mask, standard port 47808
34 | * -
35 | - 192.168.0.13:47809
36 | - IPv4, alternate port
37 | * -
38 | - 192.168.0.14/24:47809
39 | - IPv4 CIDR, alternate port
40 | * -
41 | - 192.168.0.15/255.255.255.0:47809
42 | - IPv4 subnet mask, alternate port 47809
43 | * -
44 | - 01:02:03:04:05:06
45 | - Ethernet address
46 | * -
47 | - 0x010203
48 | - VLAN address
49 | * -
50 | - [fe80::9873:c319]
51 | - IPv6
52 | * -
53 | - [fe80::9873:c319]:47809
54 | - IPv6, alternate port 47809
55 | * -
56 | - [fe80::9873:c319/64]
57 | - IPv6 network mask, standard port 47808
58 | * -
59 | - enp0s25
60 | - Interface name (ifaddr)
61 | * - Local Broadcast
62 | - \*
63 | -
64 | * - Remote Station
65 | - 100:12
66 | - ARCNET, MS/TP
67 | * -
68 | - 100:192.168.0.16
69 | - IPv4, standard port 47808
70 | * -
71 | - 100:192.168.0.17:47809
72 | - IPv4, alternate port 47809
73 | * -
74 | - 100:0x010203
75 | - VLAN address
76 | * - Remote Broadcast
77 | - 100:\*
78 | -
79 | * - Global Broadcast
80 | - \*:\*
81 | -
82 |
83 | BACpypes3 has a special addressing mode called **route aware** that allows
84 | applications to bypass the router-to-network discovery and resolution process
85 | and send a message to a specific router.
86 |
87 | .. list-table:: Route Aware Address Patterns
88 | :widths: 25 25 50
89 | :header-rows: 1
90 |
91 | * - Type
92 | - Example
93 | - Context
94 | * - Remote Station
95 | - 200:12\@192.168.0.18
96 | - ARCNET, MS/TP via IPv4
97 | * -
98 | - 200:192.168.0.19\@192.168.0.20
99 | - IPv4 via IPv4
100 | * -
101 | - 200:0x030405\@192.168.0.20
102 | - VLAN via IPv4
103 | * - Remote Broadcast
104 | - 200:\*\@192.168.0.21
105 | - ARCNET, MS/TP via IPv4
106 | * - Global Broadcast
107 | - \*:\*\@192.168.0.22
108 | - all devices via IPv4
109 |
110 |
--------------------------------------------------------------------------------
/doc/source/gettingstarted/index.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes Getting Started 1
2 |
3 | Getting Started
4 | ===============
5 |
6 | .. toctree::
7 | :maxdepth: 1
8 |
9 | setup.rst
10 | udpcommunications.rst
11 | addresses.rst
12 | objectidentifiers.rst
13 | running.rst
14 | debugging.rst
15 |
16 | This tutorial starts with just enough of the basics of BACnet to get a
17 | workstation communicating with another device. If you are already familiar
18 | with BACnet, skip to the next section.
19 |
20 |
--------------------------------------------------------------------------------
/doc/source/gettingstarted/objectidentifiers.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes3 Addresses
2 |
3 | Object and Property Identifiers
4 | ===============================
5 |
6 | Object Identifiers
7 | ------------------
8 |
9 | Every object in a BACnet device has an object identifier consisting of two
10 | parts, an object type and an instance number:
11 |
12 | .. code-block:: text
13 |
14 | object-type,instance
15 |
16 | The *object-type* is the enumeration name in the ANS.1 definition of the
17 | **BACnetObjectType** in Clause 21 such as `binary-input`, `calendar`, or
18 | `device`. The object type may also be an unsigned integer in the range 0..1023.
19 |
20 | .. note::
21 |
22 | Enumerated values 0-127 are reserved for definition by ASHRAE. Enumerated
23 | values 128-1023 may be used by others subject to the procedures and
24 | constraints described in Clause 23.
25 |
26 | The *instance* is an unsigned integer in the range 0..4194303, note that 4194303
27 | is reserved for "uninitialized."
28 |
29 | .. caution::
30 |
31 | BACpypes3 also supports the legacy BACpypes format where the object type
32 | and instance are separated by a colon ':' and the object type names are
33 | lower-camel-case such as `binaryInput`. This format is discouraged and may
34 | be deprecated in a future version of BACpypes3.
35 |
36 | Property Identifiers
37 | --------------------
38 |
39 | Every property of a BACnet object has a property identifier:
40 |
41 | .. code-block:: text
42 |
43 | property-identifier
44 |
45 | The *property-identifier* is the enumeration name in the ASN.1 definition of the
46 | **BACnetPropertyIdentifier** in Clause 21 such as `object-name`, `description`,
47 | or `present-value`. The property identifier may also be an unsigned integer in
48 | the range
49 |
50 | .. note::
51 |
52 | Enumerated values 0-511 and enumerated values 4194304 and up are reserved
53 | for definition by ASHRAE. Enumerated values 512-4194303 may be used by
54 | others subject to the procedures and constraints described in Clause 23.
55 |
56 |
--------------------------------------------------------------------------------
/doc/source/gettingstarted/udpcommunications.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes3 Addresses
2 |
3 | UDP Communications
4 | ==================
5 |
6 | BACnet devices communicate using UDP rather than TCP. This is so
7 | devices do not need to implement a full IP stack (although
8 | many of them do because they support multiple protocols, including
9 | having embedded web servers).
10 |
11 | There are two types of UDP messages; *unicast* which is a message
12 | from one specific IP address (and port) to another device's IP address
13 | (and port); and *broadcast* messages which are sent by one device
14 | and received and processed by all other devices that are listening
15 | on that port. BACnet uses both types of messages and your workstation
16 | will need to receive both types.
17 |
18 | To receive both unicast and broadcast addresses, BACpypes3
19 | opens two sockets, one for unicast traffic and one that only listens
20 | for broadcast messages. The operating system will not allow two applications
21 | to open the same socket at the same time so to run two BACnet applications at
22 | the same time they need to be configured with different ports.
23 |
24 | .. note::
25 |
26 | The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by
27 | by the `Internet Assigned Numbers Authority `_, and sequentially
28 | higher numbers are used in many applications (i.e. 47809, 47810,...).
29 | There are some BACnet routing and networking issues related to using these higher unoffical
30 | ports, but that is a topic for another tutorial.
31 |
32 |
--------------------------------------------------------------------------------
/doc/source/glossary.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes glossary
2 |
3 | Glossary
4 | ========
5 |
6 | .. glossary::
7 |
8 | BACnet
9 | BACnet (Building Automation and Control Network) is the global data
10 | communications standard for building automation and control networks.
11 | It provides a vendor-independent networking solution to enable
12 | interoperability among equipment and control devices for a wide range
13 | of building automation applications. BACnet enables interoperability
14 | by defining communications messages, formats and rules for exchanging
15 | data, commands, and status information. BACnet provides the data
16 | communications infrastructure for intelligent buildings and is
17 | implemented in hundreds of thousands of buildings around the world.
18 |
19 | The BACnet standard was developed and is continuously maintained by the
20 | BACnet Committee, more formally known as SSPC 135 (a Standing Standards
21 | Project Committee) of the American Society of Heating, Refrigerating
22 | and Air-Conditioning Engineers (ASHRAE).
23 |
24 | BACnet is an ISO standard (EN ISO 16484-5), a European standard (DIN EN
25 | ISO 16484-5:2017-12) and a national standard in many countries.
26 |
27 | BACnet device
28 | Any device, real or virtual, that supports digital communication using
29 | the BACnet protocol.
30 |
31 | BACnet network
32 | A network of BACnet devices that share the MAC or VMAC address space
33 | under a particular BACnet network number.
34 |
35 | BACnet internetwork
36 | A set of two or more networks interconnected by BACnet routers. In a
37 | BACnet internetwork interconnected by BACnet routers, there exists
38 | exactly one message path between any two nodes.
39 |
40 | BACnet Device
41 | Any device, real or virtual, that supports digital communication using
42 | the BACnet protocol.
43 |
44 | ephemeral port
45 | An ephemeral port is a communications endpoint (port) of a transport
46 | layer protocol of the Internet protocol suite that is used for only a
47 | short period of time for the duration of a communication session.
48 | Such short-lived ports are allocated automatically within a predefined
49 | range of port numbers by the IP stack software of a computer operating
50 | system. `Wikipedia `_
51 |
52 | upstream
53 | Something going up a stack from a server to client.
54 |
55 | downstream
56 | Something going down a stack from a client to a server.
57 |
58 | stack
59 | A sequence of communication objects organized in a semi-linear sequence
60 | from the application layer at the top to the physical networking layer(s)
61 | at the bottom.
62 |
63 | discoverable
64 | Something that can be determined using a combination of BACnet objects,
65 | properties and services. For example, discovering the network topology
66 | by using Who-Is-Router-To-Network, or knowing what objects are defined
67 | in a device by reading the *object-list* property.
68 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. BACpypes3 documentation master file, created by
2 | sphinx-quickstart on Sat Mar 11 00:14:30 2023.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to BACpypes3!
7 | =====================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | BACpypes3 is a library for building Python applications that communicate using
14 | the :term:`BACnet` protocol. Installation is easy::
15 |
16 | $ pip install bacpypes3
17 |
18 | You will be installing the latest released version from
19 | `PyPI `_. You can also check out the
20 | latest version from `GitHub `_::
21 |
22 | $ git clone https://github.com/JoelBender/BACpypes3.git
23 | $ cd BACpypes3
24 |
25 | And then use the `pipenv `_ utility to create
26 | a virtual environment, install all of the developer tools, then activate it::
27 |
28 | $ pipenv install --dev
29 | $ pipenv shell
30 |
31 | .. note::
32 |
33 | If you would like to participate in development, please join
34 | the chat room on `Gitter `_.
35 |
36 | Getting Started
37 | ---------------
38 |
39 | The **bacpypes3** module can be run directly. It will create a small
40 | :term:`stack` of communications objects that are bound together as a
41 | :term:`BACnet device` and present a shell-like prompt::
42 |
43 | $ python3 -m bacpypes3
44 | >
45 |
46 | The module has a variety options for configuring the small stack for different
47 | environments::
48 |
49 | $ python3 -m bacpypes3 --help
50 |
51 | And the shell has commands that are useful for examining the local
52 | :term:`BACnet internetwork`, its topology, the connected devices, and their
53 | objects::
54 |
55 | > help
56 |
57 | .. toctree::
58 | :maxdepth: 2
59 |
60 | gettingstarted/index.rst
61 | samples/index.rst
62 |
63 |
64 | Glossary
65 | --------
66 |
67 | .. toctree::
68 | :maxdepth: 2
69 |
70 | glossary.rst
71 |
72 |
73 | Indices and tables
74 | ==================
75 |
76 | * :ref:`genindex`
77 | * :ref:`modindex`
78 | * :ref:`search`
79 |
--------------------------------------------------------------------------------
/doc/source/samples/apdu-hex-decode.rst:
--------------------------------------------------------------------------------
1 | .. apdu-hex-decode.py sample application
2 |
3 | .. _apdu-hex-decode.py:
4 |
5 | apdu-hex-decode.py
6 | ==================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/client-server.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/samples/client-server.odg
--------------------------------------------------------------------------------
/doc/source/samples/client-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoelBender/BACpypes3/0b5c12c3a8f9cdf0226a63dc3e711b11dfd696d2/doc/source/samples/client-server.png
--------------------------------------------------------------------------------
/doc/source/samples/cmd-address.rst:
--------------------------------------------------------------------------------
1 | .. cmd-address.py sample application
2 |
3 | cmd-address.py
4 | ==============
5 |
6 | This is a long line of text.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/cmd-debugging.rst:
--------------------------------------------------------------------------------
1 | .. cmd-debugging.py sample application
2 |
3 | cmd-debugging.py
4 | ================
5 |
6 | This is a long line of test.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/cmd-samples.rst:
--------------------------------------------------------------------------------
1 | .. cmd-samples sample application
2 |
3 | cmd-samples.py
4 | ==============
5 |
6 | This is a long line of text.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/console-ase-sap.rst:
--------------------------------------------------------------------------------
1 | .. console-ase-sap.py sample application
2 |
3 | .. _console-ase-sap.py:
4 |
5 | console-ase-sap.py
6 | ==================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/console-ini.rst:
--------------------------------------------------------------------------------
1 | .. console-ini.py sample application
2 |
3 | .. _console-ini.py:
4 |
5 | console-ini.py
6 | ==============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/console-json.rst:
--------------------------------------------------------------------------------
1 | .. console-json.py sample application
2 |
3 | .. _console-json.py:
4 |
5 | console-json.py
6 | ===============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/console-prompt.rst:
--------------------------------------------------------------------------------
1 | .. console-prompt.py sample application
2 |
3 | .. _console-prompt.py:
4 |
5 | console-prompt.py
6 | =================
7 |
8 | This is almost identical to the :ref:`console.py` example, all this does is
9 | add a `\-\-prompt` option.
10 |
11 | The `ArgumentParser` class in BACpypes3 is a subclass of the built-in class
12 | with the same name, so extending it is simple::
13 |
14 | args = ArgumentParser().parse_args()
15 |
16 | The code is replaced with this::
17 |
18 | # add a way to change the console prompt
19 | parser = ArgumentParser()
20 | parser.add_argument(
21 | "--prompt",
22 | type=str,
23 | help="change the prompt",
24 | default="> ",
25 | )
26 | args = parser.parse_args()
27 |
28 | And the `Console` constructor changes from this::
29 |
30 | console = Console()
31 |
32 | to include the prompt::
33 |
34 | console = Console(prompt=args.prompt)
35 |
36 | Turning on debugging shows the new argument along with the others that are
37 | built-in::
38 |
39 | $ python3 console-prompt.py --prompt '? ' --debug
40 | DEBUG:__main__:args: Namespace(..., prompt='? ')
41 | DEBUG:__main__:settings: {'debug': ['__main__'], 'color': False, ... }
42 | ?
43 |
44 |
--------------------------------------------------------------------------------
/doc/source/samples/console-yaml.rst:
--------------------------------------------------------------------------------
1 | .. console-yaml.py sample application
2 |
3 | .. _console-yaml.py:
4 |
5 | console-yaml.py
6 | ===============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/cov-client.rst:
--------------------------------------------------------------------------------
1 | .. cov-client.py sample application
2 |
3 | .. _cov-client.py:
4 |
5 | cov-client.py
6 | =============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/cov-server.rst:
--------------------------------------------------------------------------------
1 | .. cov-server.py sample application
2 |
3 | .. _cov-server.py:
4 |
5 | cov-server.py
6 | =============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/custom-cache.rst:
--------------------------------------------------------------------------------
1 | .. custom-cache.py sample application
2 |
3 | .. _custom-cache.py:
4 |
5 | custom-cache.py
6 | ===============
7 |
8 | This application *is a start* of a custom device information cache implementation
9 | that uses Redis as a shared cache for DeviceInfo records.
10 |
11 | The Redis keys will be `bacnet:dev:address` where `address` is the address of
12 | the device and may include the network like `10:1.2.3.4` for an IPv4 device
13 | on network 10.
14 |
15 | This application is also *a start* of a custom routing information cache
16 | implementation that also uses Redis as a shared cache for Who-Is-Router-To-Network
17 | content. When a request is sent to a remote station or remote broadcast, the
18 | cache is used to return the address of the router to the destination network.
19 |
20 | The Redis keys will be `bacnet:rtn:snet:dnet` and the key value is the address
21 | of the router on the `snet` that is the router to the `dnet`.
22 |
23 |
--------------------------------------------------------------------------------
/doc/source/samples/custom-client.rst:
--------------------------------------------------------------------------------
1 | .. custom-client.py sample application
2 |
3 | .. _custom-client.py:
4 |
5 | custom-client.py
6 | ================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/custom-server.rst:
--------------------------------------------------------------------------------
1 | .. custom-server.py sample application
2 |
3 | .. _custom-server.py:
4 |
5 | custom-server.py
6 | ================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/custom.rst:
--------------------------------------------------------------------------------
1 | .. custom.py sample application
2 |
3 | .. _custom.py:
4 |
5 | custom.py
6 | =========
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/discover-devices.rst:
--------------------------------------------------------------------------------
1 | .. discover-devices.py sample application
2 |
3 | .. _discover-devices.py:
4 |
5 | discover-devices.py
6 | ===================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/discover-objects.rst:
--------------------------------------------------------------------------------
1 | .. discover-objects.py sample application
2 |
3 | .. _discover-objects.py:
4 |
5 | discover-objects.py
6 | ===================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/docker/index.rst:
--------------------------------------------------------------------------------
1 | .. docker samples
2 |
3 | Docker Samples
4 | ==============
5 |
6 | Running BACnet applications in docker is a challenge because the
7 | environment that is normally presented to applications is not the
8 | one that the host has because the intent is to protect containers
9 | (running images) from interfering with or being interfered with
10 | by other devices than the host.
11 |
12 | Docker has a `\-\-network host `_
13 | option so the container does not get its own IP address allocated,
14 | which is helpful, but only for unicast traffic. Attempts to "bind" to
15 | broadcast addresses of the host fail.
16 |
17 | The solution is to have BACpypes3 applications be configured as
18 | a foreign device and register with a BBMD on the network. To allow
19 | more than one application running on the same host, the application
20 | uses an :term:`ephemeral port` and the operating system will proved
21 | provide an unused socket.
22 |
23 | A side effect is that subsequent runs of the same image will present
24 | most likely not have the same port number, so they will have different
25 | BACnet/IPv4 addresses each time they are run. During debugging it
26 | might be beneficial to assign a fixed socket number.
27 |
28 | This also means that the container can register as a foreign device
29 | with some BBMD in the BACnet intranet.
30 |
31 | Setting Up
32 | ----------
33 |
34 | The docker samples are often used with unpublished versions of
35 | BACpypes3, so the build scripts include a local build of the
36 | package as a wheel.
37 |
38 | The root folder of the BACpypes3 project has a `bdist.sh` script
39 | which builds Python eggs and a wheel.
40 |
41 |
--------------------------------------------------------------------------------
/doc/source/samples/read-batch.rst:
--------------------------------------------------------------------------------
1 | .. read-batch.py sample application
2 |
3 | .. _read-batch.py:
4 |
5 | read-batch.py
6 | =============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/read-bbmd.rst:
--------------------------------------------------------------------------------
1 | .. read-bbmd.py sample application
2 |
3 | .. _read-bbmd.py:
4 |
5 | read-bbmd.py
6 | ============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/read-property.rst:
--------------------------------------------------------------------------------
1 | .. read-property.py sample application
2 |
3 | .. _read-property.py:
4 |
5 | read-property.py
6 | ================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/router-json.rst:
--------------------------------------------------------------------------------
1 | .. router-json.py sample application
2 |
3 | .. _router-json.py:
4 |
5 | router-json.py
6 | ==============
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/start-here.rst:
--------------------------------------------------------------------------------
1 | .. start-here.py sample application
2 |
3 | .. _start-here.py:
4 |
5 | start-here.py
6 | =============
7 |
8 | This is a good place to start.
9 |
10 | BACnet is a peer-to-peer protocol so devices can be clients (issue requests) and
11 | servers (provide responses) and are often both at the same time. This sample
12 | has just enough code to make itself known on the network and can be used as
13 | a starting point for predominantly client-like applications (reading data from
14 | other devices) or server-like applications (gateways).
15 |
16 | The `SimpleArgumentParser` is the same one the module uses, see :ref:`running`::
17 |
18 | args = SimpleArgumentParser().parse_args()
19 | if _debug:
20 | _log.debug("args: %r", args)
21 |
22 | The `Application` class has different class methods for building an application
23 | stack, the `from_args()` method is looks for the options from the simple
24 | argument parser. Custom applications can add additional options and/or use
25 | their own subclass::
26 |
27 | # build an application
28 | app = Application.from_args(args)
29 | if _debug:
30 | _log.debug("app: %r", app)
31 |
32 | Server-like applications just run::
33 |
34 | # like running forever
35 | await asyncio.Future()
36 |
37 | .. note::
38 |
39 | The `ReinitializeDevice` service can be used to quit the current application
40 | which is in turn wrapped by some supervisory service that automatically
41 | restarts it such as
42 | `systemd `_
43 | or `docker `_.
44 |
--------------------------------------------------------------------------------
/doc/source/samples/vlan-console-1.rst:
--------------------------------------------------------------------------------
1 | .. vlan-console-1.py sample application
2 |
3 | vlan-console-1.py
4 | =================
5 |
6 | This is a long line of text.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/vlan-console-2.rst:
--------------------------------------------------------------------------------
1 | .. vlan-console-2.py sample application
2 |
3 | vlan-console-2.py
4 | =================
5 |
6 | This is a long line of text.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/vlan-console-3.rst:
--------------------------------------------------------------------------------
1 | .. vlan-console-3.py sample application
2 |
3 | vlan-console-3.py
4 | =================
5 |
6 | This is a long line of text.
7 |
--------------------------------------------------------------------------------
/doc/source/samples/who-has.rst:
--------------------------------------------------------------------------------
1 | .. who-has.py sample application
2 |
3 | .. _who-has.py:
4 |
5 | who-has.py
6 | ==========
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/doc/source/samples/write-property.rst:
--------------------------------------------------------------------------------
1 | .. write-property.py sample application
2 |
3 | .. _write-property.py:
4 |
5 | write-property.py
6 | =================
7 |
8 | This is a long line of text.
9 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel"]
3 | build-backend = "setuptools.build_meta:__legacy__"
4 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | asyncio_mode = auto
3 | asyncio_default_fixture_loop_scope = function
4 |
--------------------------------------------------------------------------------
/release_to_pypi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # build a distribution
4 | . bdist.sh
5 |
6 | read -p "Upload to PyPI? [y/n/x] " yesno || exit 1
7 |
8 | if [ "$yesno" = "y" ] ;
9 | then
10 | python3 -m twine upload dist/*
11 | elif [ "$yesno" = "n" ] ;
12 | then
13 | echo "Skipped..."
14 | else
15 | echo "exit..."
16 | exit 1
17 | fi
18 |
--------------------------------------------------------------------------------
/release_to_testpypi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # build a distribution
4 | . build_dist.sh
5 |
6 | read -p "Upload to Test PyPI? [y/n/x] " yesno || exit 1
7 |
8 | if [ "$yesno" = "y" ] ;
9 | then
10 | twine upload -r testpypi --config-file .pypirc dist/*
11 | elif [ "$yesno" = "n" ] ;
12 | then
13 | echo "Skipped..."
14 | else
15 | echo "exit..."
16 | exit 1
17 | fi
18 |
19 |
--------------------------------------------------------------------------------
/samples/BACpypes.ini:
--------------------------------------------------------------------------------
1 | [BACpypes]
2 | color: false
3 |
--------------------------------------------------------------------------------
/samples/BACpypes.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "BACpypes": {
4 | "color": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/samples/BACpypes.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | BACpypes:
3 | color: false
4 |
--------------------------------------------------------------------------------
/samples/apdu-hex-decode.py:
--------------------------------------------------------------------------------
1 | """
2 | Given a hex-binary encoded string from the variable portion of an APDU, decode
3 | it into into a tag list and dump out the result.
4 | """
5 |
6 | import sys
7 | from bacpypes3.debugging import xtob
8 | from bacpypes3.pdu import PDUData
9 | from bacpypes3.primitivedata import TagList
10 |
11 | b = xtob(sys.argv[1])
12 | d = PDUData(b)
13 |
14 | tl = TagList.decode(d)
15 | for i, t in enumerate(tl):
16 | print(f"[{i}]")
17 | t.debug_contents()
18 |
--------------------------------------------------------------------------------
/samples/cmd-address.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Address parameters for command line interpreters
5 | """
6 |
7 | import sys
8 | import asyncio
9 |
10 | from typing import Callable, Optional, Union
11 |
12 | from bacpypes3.settings import settings
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 |
16 | from bacpypes3.console import Console
17 | from bacpypes3.cmd import Cmd
18 | from bacpypes3.comm import bind
19 | from bacpypes3.pdu import Address, IPv4Address, IPv6Address
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class SampleCmd(Cmd):
28 | """
29 | Sample Console Command utility demonstrating addresses support in bacpypes3
30 | Using the typing module, bacpypes3 can evaluate at runtime the argument
31 | passed to the function then return the right type (or an exception).
32 | """
33 |
34 | _debug: Callable[..., None]
35 |
36 | async def do_test1(self, addr: Address) -> None:
37 | """
38 | test1 requires an address argument that will be evaluated as a
39 | bacpypes3.pdu.Address, and if that interpretation is successful,
40 | provided as the parameter value.
41 |
42 | usage: test1 addr
43 |
44 | ex. test1 2:4 or test1 192.168.1.2
45 | """
46 | if _debug:
47 | SampleCmd._debug("do_test1 %r", addr)
48 |
49 | await self.response(f"test1 {addr!r}")
50 |
51 | async def do_test2(self, addr: Optional[Address] = None) -> None:
52 | """
53 | test2 is similar to test1 with an optional address.
54 |
55 | usage: test2 [ addr:Address ]
56 |
57 | ex. test2 2:4 or test2 192.168.1.2 or test2
58 | """
59 | if _debug:
60 | SampleCmd._debug("do_test2 %r", addr)
61 |
62 | await self.response(f"test2 {addr!r}")
63 |
64 | async def do_test3(self, addr: Union[IPv4Address, IPv6Address]) -> None:
65 | """
66 | This varient accepts only IPv4 or IPv6 addresses or will raise an
67 | exception.
68 |
69 | usage: test3 addr
70 | """
71 | if _debug:
72 | SampleCmd._debug("do_test3 %r", addr)
73 |
74 | await self.response(f"test3 {addr!r}")
75 |
76 |
77 | async def main() -> None:
78 | try:
79 | console = None
80 | args = ArgumentParser().parse_args()
81 | if _debug:
82 | _log.debug("args: %r", args)
83 | _log.debug("settings: %r", settings)
84 |
85 | # build a very small stack
86 | console = Console()
87 | cmd = SampleCmd()
88 | bind(console, cmd)
89 |
90 | # run until the console is done, canceled or EOF
91 | await console.fini.wait()
92 |
93 | finally:
94 | if console and console.exit_status:
95 | sys.exit(console.exit_status)
96 |
97 |
98 | if __name__ == "__main__":
99 | asyncio.run(main())
100 |
--------------------------------------------------------------------------------
/samples/cmd-debugging.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | This application adds the CmdDebugging mixin class to provide support for
5 | additional debugging commands.
6 | """
7 |
8 | import sys
9 | import asyncio
10 |
11 | from typing import Callable
12 |
13 | from bacpypes3.settings import settings
14 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
15 | from bacpypes3.argparse import ArgumentParser
16 |
17 | from bacpypes3.console import Console
18 | from bacpypes3.cmd import Cmd, CmdDebugging
19 | from bacpypes3.comm import bind
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class SampleCmd(Cmd, CmdDebugging):
28 | _debug: Callable[..., None]
29 |
30 | """
31 | Sample Cmd
32 | """
33 |
34 | async def do_hello(self) -> None:
35 | """
36 | usage: hello
37 | """
38 | await self.response("Hello!")
39 |
40 |
41 | async def main() -> None:
42 | try:
43 | console = None
44 | args = ArgumentParser().parse_args()
45 | if _debug:
46 | _log.debug("args: %r", args)
47 | _log.debug("settings: %r", settings)
48 |
49 | # build a very small stack
50 | console = Console()
51 | cmd = SampleCmd()
52 | bind(console, cmd)
53 |
54 | # run until the console is done, canceled or EOF
55 | await console.fini.wait()
56 |
57 | finally:
58 | if console and console.exit_status:
59 | sys.exit(console.exit_status)
60 |
61 |
62 | if __name__ == "__main__":
63 | asyncio.run(main())
64 |
--------------------------------------------------------------------------------
/samples/console-ase-sap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that forwards the input to a service and echos the
5 | response. The service converts the requests to uppercase.
6 | """
7 |
8 | import sys
9 | import asyncio
10 |
11 | from typing import Callable
12 |
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 | from bacpypes3.console import Console, ConsolePDU
16 | from bacpypes3.comm import Server, ApplicationServiceElement, ServiceAccessPoint, bind
17 |
18 | # some debugging
19 | _debug = 0
20 | _log = ModuleLogger(globals())
21 |
22 |
23 | @bacpypes_debugging
24 | class Echo(Server[ConsolePDU], ApplicationServiceElement):
25 | """
26 | Simple example server that echos the downstream strings as uppercase
27 | strings going upstream. If the PDU is None the console is finished,
28 | and this could send an integer status code upstream to exit.
29 | """
30 |
31 | _debug: Callable[..., None]
32 |
33 | async def indication(self, pdu: ConsolePDU) -> None:
34 | if _debug:
35 | Echo._debug("indication {!r}".format(pdu))
36 | if pdu is None:
37 | return
38 |
39 | await self.request(pdu)
40 |
41 | async def confirmation(self, pdu: ConsolePDU) -> None:
42 | if _debug:
43 | Echo._debug("confirmation {!r}".format(pdu))
44 | if pdu is None:
45 | return
46 |
47 | await self.response(pdu.upper())
48 |
49 |
50 | @bacpypes_debugging
51 | class EchoServiceAccessPoint(ServiceAccessPoint):
52 |
53 | _debug: Callable[..., None]
54 |
55 | async def sap_indication(self, pdu: str) -> None:
56 | if _debug:
57 | EchoServiceAccessPoint._debug("sap_indication {!r}".format(pdu))
58 | await self.sap_response(pdu.upper())
59 |
60 |
61 | async def main() -> None:
62 | try:
63 | console = None
64 | ArgumentParser().parse_args()
65 |
66 | # build a very small stack
67 | console = Console()
68 | echo = Echo()
69 | echo_service = EchoServiceAccessPoint()
70 | bind(console, echo, echo_service) # type: ignore[misc]
71 |
72 | # run until the console is done, canceled or EOF
73 | await console.fini.wait()
74 |
75 | except KeyboardInterrupt:
76 | if _debug:
77 | _log.debug("keyboard interrupt")
78 | finally:
79 | if console and console.exit_status:
80 | sys.exit(console.exit_status)
81 |
82 |
83 | if __name__ == "__main__":
84 | asyncio.run(main())
85 |
--------------------------------------------------------------------------------
/samples/console-ini.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos the input converted to uppercase. This is a
5 | derivative of the console.py sample that looks for a BACpypes.ini settings
6 | file, or the INI file specified with the --ini option.
7 | """
8 |
9 | import sys
10 | import asyncio
11 |
12 | from typing import Callable
13 |
14 | from bacpypes3.settings import settings
15 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
16 | from bacpypes3.argparse import INIArgumentParser
17 | from bacpypes3.console import Console, ConsolePDU
18 | from bacpypes3.comm import Server, bind
19 |
20 | # some debugging
21 | _debug = 0
22 | _log = ModuleLogger(globals())
23 |
24 |
25 | @bacpypes_debugging
26 | class Echo(Server[ConsolePDU]):
27 | """
28 | Simple example server that echos the downstream strings as uppercase
29 | strings going upstream. If the PDU is None the console is finished,
30 | and this could send an integer status code upstream to exit.
31 | """
32 |
33 | _debug: Callable[..., None]
34 |
35 | async def indication(self, pdu: ConsolePDU) -> None:
36 | if _debug:
37 | Echo._debug("indication {!r}".format(pdu))
38 | if pdu is None:
39 | return
40 |
41 | await self.response(pdu.upper())
42 |
43 |
44 | async def main() -> None:
45 | try:
46 | console = None
47 | parser = INIArgumentParser()
48 | args = parser.parse_args()
49 | if _debug:
50 | _log.debug("args: %r", args)
51 | _log.debug("settings: %r", settings)
52 |
53 | # build a very small stack
54 | console = Console()
55 | echo = Echo()
56 | bind(console, echo)
57 |
58 | # run until the console is done, canceled or EOF
59 | await console.fini.wait()
60 |
61 | finally:
62 | if console and console.exit_status:
63 | sys.exit(console.exit_status)
64 |
65 |
66 | if __name__ == "__main__":
67 | asyncio.run(main())
68 |
--------------------------------------------------------------------------------
/samples/console-json.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos the input converted to uppercase. This is a
5 | derivative of the console.py sample that looks for a BACpypes.json settings
6 | file, or the JSON file specified with the --json option.
7 | """
8 |
9 | import sys
10 | import asyncio
11 |
12 | from typing import Callable
13 |
14 | from bacpypes3.settings import settings
15 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
16 | from bacpypes3.argparse import JSONArgumentParser
17 | from bacpypes3.console import Console, ConsolePDU
18 | from bacpypes3.comm import Server, bind
19 |
20 | # some debugging
21 | _debug = 0
22 | _log = ModuleLogger(globals())
23 |
24 |
25 | @bacpypes_debugging
26 | class Echo(Server[ConsolePDU]):
27 | """
28 | Simple example server that echos the downstream strings as uppercase
29 | strings going upstream. If the PDU is None the console is finished,
30 | and this could send an integer status code upstream to exit.
31 | """
32 |
33 | _debug: Callable[..., None]
34 |
35 | async def indication(self, pdu: ConsolePDU) -> None:
36 | if _debug:
37 | Echo._debug("indication {!r}".format(pdu))
38 | if pdu is None:
39 | return
40 |
41 | await self.response(pdu.upper())
42 |
43 |
44 | async def main() -> None:
45 | try:
46 | console = None
47 | parser = JSONArgumentParser()
48 | args = parser.parse_args()
49 | if _debug:
50 | _log.debug("args: %r", args)
51 | _log.debug("settings: %r", settings)
52 |
53 | # build a very small stack
54 | console = Console()
55 | echo = Echo()
56 | bind(console, echo)
57 |
58 | # run until the console is done, canceled or EOF
59 | await console.fini.wait()
60 |
61 | finally:
62 | if console and console.exit_status:
63 | sys.exit(console.exit_status)
64 |
65 |
66 | if __name__ == "__main__":
67 | asyncio.run(main())
68 |
--------------------------------------------------------------------------------
/samples/console-prompt.py:
--------------------------------------------------------------------------------
1 | """
2 | Same as the `console.py` application but adds a `--prompt` option to
3 | change the prompt.
4 | """
5 |
6 | import sys
7 | import asyncio
8 |
9 | from typing import Callable
10 |
11 | from bacpypes3.settings import settings
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.argparse import ArgumentParser
14 | from bacpypes3.console import Console, ConsolePDU
15 | from bacpypes3.comm import Server, bind
16 |
17 | # some debugging
18 | _debug = 0
19 | _log = ModuleLogger(globals())
20 |
21 |
22 | @bacpypes_debugging
23 | class Echo(Server[ConsolePDU]):
24 | _debug: Callable[..., None]
25 |
26 | async def indication(self, pdu: ConsolePDU) -> None:
27 | if _debug:
28 | Echo._debug("indication {!r}".format(pdu))
29 | if pdu is None:
30 | return
31 |
32 | assert isinstance(pdu, str)
33 | await self.response(pdu.upper())
34 |
35 |
36 | async def main() -> None:
37 | try:
38 | console = None
39 |
40 | # add a way to change the console prompt
41 | parser = ArgumentParser()
42 | parser.add_argument(
43 | "--prompt",
44 | type=str,
45 | help="change the prompt",
46 | default="> ",
47 | )
48 |
49 | args = parser.parse_args()
50 | if _debug:
51 | _log.debug("args: %r", args)
52 | _log.debug("settings: %r", settings)
53 |
54 | # build a very small stack
55 | console = Console(prompt=args.prompt)
56 | echo = Echo()
57 | bind(console, echo)
58 |
59 | # run until the console is done, canceled or EOF
60 | await console.fini.wait()
61 |
62 | finally:
63 | if console and console.exit_status:
64 | sys.exit(console.exit_status)
65 |
66 |
67 | if __name__ == "__main__":
68 | asyncio.run(main())
69 |
--------------------------------------------------------------------------------
/samples/console-yaml.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos the input converted to uppercase. This is a
5 | derivative of the console.py sample that looks for a BACpypes.yml settings
6 | file, or the YAML file specified with the --yaml option.
7 | """
8 |
9 | import sys
10 | import asyncio
11 |
12 | from typing import Callable
13 |
14 | from bacpypes3.settings import settings
15 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
16 | from bacpypes3.argparse import YAMLArgumentParser
17 | from bacpypes3.console import Console, ConsolePDU
18 | from bacpypes3.comm import Server, bind
19 |
20 | # some debugging
21 | _debug = 0
22 | _log = ModuleLogger(globals())
23 |
24 |
25 | @bacpypes_debugging
26 | class Echo(Server[ConsolePDU]):
27 | """
28 | Simple example server that echos the downstream strings as uppercase
29 | strings going upstream. If the PDU is None the console is finished,
30 | and this could send an integer status code upstream to exit.
31 | """
32 |
33 | _debug: Callable[..., None]
34 |
35 | async def indication(self, pdu: ConsolePDU) -> None:
36 | if _debug:
37 | Echo._debug("indication {!r}".format(pdu))
38 | if pdu is None:
39 | return
40 |
41 | assert isinstance(pdu, str)
42 | await self.response(pdu.upper())
43 |
44 |
45 | async def main() -> None:
46 | try:
47 | console = None
48 | parser = YAMLArgumentParser()
49 | args = parser.parse_args()
50 | if _debug:
51 | _log.debug("args: %r", args)
52 | _log.debug("settings: %r", settings)
53 |
54 | # build a very small stack
55 | console = Console()
56 | echo = Echo()
57 | bind(console, echo)
58 |
59 | # run until the console is done, canceled or EOF
60 | await console.fini.wait()
61 |
62 | finally:
63 | if console and console.exit_status:
64 | sys.exit(console.exit_status)
65 |
66 |
67 | if __name__ == "__main__":
68 | asyncio.run(main())
69 |
--------------------------------------------------------------------------------
/samples/console.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that echos the input converted to uppercase.
3 | """
4 |
5 | import sys
6 | import asyncio
7 |
8 | from typing import Callable
9 |
10 | from bacpypes3.settings import settings
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.argparse import ArgumentParser
13 | from bacpypes3.console import Console, ConsolePDU
14 | from bacpypes3.comm import Server, bind
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 |
21 | @bacpypes_debugging
22 | class Echo(Server[ConsolePDU]):
23 | """
24 | This example server echos downstream strings as uppercase strings going
25 | upstream. If the PDU is None the console is finished, and this could send
26 | an integer status code upstream to exit.
27 | """
28 |
29 | _debug: Callable[..., None]
30 |
31 | async def indication(self, pdu: ConsolePDU) -> None:
32 | """
33 | This function is called with each line of text from the console (or
34 | from a file or pipe) and called with None at end-of-file. It is
35 | "downstream" of the Console() instance and gets this "indication" when
36 | the console is making a "request".
37 | """
38 | if _debug:
39 | Echo._debug("indication {!r}".format(pdu))
40 | if pdu is None:
41 | return
42 |
43 | # send the uppercase content back up the stack
44 | await self.response(pdu.upper())
45 |
46 |
47 | async def main() -> None:
48 | try:
49 | console = None
50 | args = ArgumentParser().parse_args()
51 | if _debug:
52 | _log.debug("args: %r", args)
53 | _log.debug("settings: %r", settings)
54 |
55 | # build a very small stack
56 | console = Console()
57 | echo = Echo()
58 | if _debug:
59 | _log.debug("console, echo: %r, %r", console, echo)
60 |
61 | # bind the two objects together, top down
62 | bind(console, echo)
63 |
64 | # run until the console is done, canceled or EOF
65 | await console.fini.wait()
66 |
67 | finally:
68 | if console and console.exit_status:
69 | sys.exit(console.exit_status)
70 |
71 |
72 | if __name__ == "__main__":
73 | asyncio.run(main())
74 |
--------------------------------------------------------------------------------
/samples/cov-client.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that creates a change-of-value context and dumps out the
3 | property identifier and its value from the notifications it receives.
4 |
5 | The device address and monitored object identifier are the only required
6 | parameters to `change_of_value()`.
7 | """
8 |
9 | import asyncio
10 | import signal
11 |
12 | from bacpypes3.debugging import ModuleLogger
13 | from bacpypes3.argparse import SimpleArgumentParser
14 | from bacpypes3.app import Application
15 |
16 | from bacpypes3.pdu import Address
17 | from bacpypes3.primitivedata import ObjectIdentifier
18 |
19 | # some debugging
20 | _debug = 0
21 | _log = ModuleLogger(globals())
22 |
23 |
24 | async def main() -> None:
25 | app = None
26 | try:
27 | parser = SimpleArgumentParser()
28 | parser.add_argument(
29 | "device_address",
30 | help="address of the server (B-device)",
31 | )
32 | parser.add_argument(
33 | "object_identifier",
34 | help="object identifier",
35 | )
36 | parser.add_argument(
37 | "--process-identifier",
38 | help="subscriber process identifier",
39 | )
40 | parser.add_argument(
41 | "--confirmed",
42 | action="store_true",
43 | help="issues confirmed notifications",
44 | )
45 | parser.add_argument(
46 | "--lifetime",
47 | help="lifetime",
48 | )
49 | args = parser.parse_args()
50 | if _debug:
51 | _log.debug("args: %r", args)
52 |
53 | # interpret the address
54 | device_address = Address(args.device_address)
55 | if _debug:
56 | _log.debug("device_address: %r", device_address)
57 |
58 | # interpret the object identifier
59 | object_identifier = ObjectIdentifier(args.object_identifier)
60 | if _debug:
61 | _log.debug("object_identifier: %r", object_identifier)
62 |
63 | # build an application
64 | app = Application.from_args(args)
65 | if _debug:
66 | _log.debug("app: %r", app)
67 |
68 | # add a Ctrl-C signal handler to terminate the application
69 | fini = asyncio.Event()
70 | loop: asyncio.events.AbstractEventLoop = asyncio.get_running_loop()
71 | loop.add_signal_handler(signal.SIGINT, lambda: fini.set())
72 |
73 | try:
74 | # use a SubscriptionContextManager
75 | async with app.change_of_value(
76 | device_address,
77 | object_identifier,
78 | args.process_identifier,
79 | args.confirmed,
80 | args.lifetime,
81 | ) as scm:
82 | if _debug:
83 | _log.debug(" - scm: %r", scm)
84 |
85 | # do something with what is received
86 | while not fini.is_set():
87 | property_identifier, property_value = await scm.get_value()
88 | print(f"{property_identifier} {property_value}")
89 |
90 | if _debug:
91 | _log.debug("exited context")
92 | except Exception as err:
93 | if _debug:
94 | _log.debug(" - exception: %r", err)
95 | finally:
96 | if app:
97 | app.close()
98 |
99 |
100 | if __name__ == "__main__":
101 | asyncio.run(main())
102 |
--------------------------------------------------------------------------------
/samples/custom-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that has a device object and an additional custom object.
3 | """
4 |
5 | import asyncio
6 |
7 | from bacpypes3.debugging import ModuleLogger
8 | from bacpypes3.argparse import SimpleArgumentParser
9 | from bacpypes3.ipv4.app import Application
10 |
11 | # this server has custom objects
12 | import custom
13 |
14 | # some debugging
15 | _debug = 0
16 | _log = ModuleLogger(globals())
17 |
18 |
19 | async def main() -> None:
20 | try:
21 | app = None
22 | args = SimpleArgumentParser().parse_args()
23 |
24 | # make sure the vendor identifier is the custom one
25 | args.vendoridentifier = custom._vendor_id
26 | if _debug:
27 | _log.debug("args: %r", args)
28 |
29 | # build an application
30 | app = Application.from_args(args)
31 | if _debug:
32 | _log.debug("app: %r", app)
33 |
34 | # create a custom object
35 | custom_object = custom.ProprietaryObject(
36 | objectIdentifier=("custom_object", 12),
37 | objectName="Wowzers!",
38 | custom_property=13,
39 | )
40 | if _debug:
41 | _log.debug("custom_object: %r", custom_object)
42 |
43 | app.add_object(custom_object)
44 |
45 | # like running forever
46 | await asyncio.Future()
47 |
48 | finally:
49 | if app:
50 | app.close()
51 |
52 |
53 | if __name__ == "__main__":
54 | asyncio.run(main())
55 |
--------------------------------------------------------------------------------
/samples/custom.py:
--------------------------------------------------------------------------------
1 | """
2 | Custom Objects and Properties
3 | """
4 |
5 | from bacpypes3.debugging import ModuleLogger
6 | from bacpypes3.primitivedata import Integer, ObjectType
7 | from bacpypes3.basetypes import PropertyIdentifier
8 | from bacpypes3.vendor import VendorInfo
9 |
10 | from bacpypes3.local.object import _Object
11 | from bacpypes3.local.device import DeviceObject as _DeviceObject
12 | from bacpypes3.local.networkport import NetworkPortObject as _NetworkPortObject
13 |
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | # this vendor identifier reference is used when registering custom classes
21 | _vendor_id = 888
22 |
23 |
24 | class ProprietaryObjectType(ObjectType):
25 | """
26 | This is a list of the object type enumerations for proprietary object types,
27 | see Clause 23.4.1.
28 | """
29 |
30 | custom_object = 128
31 |
32 |
33 | class ProprietaryPropertyIdentifier(PropertyIdentifier):
34 | """
35 | This is a list of the property identifiers that are used in custom object
36 | types or are used in custom properties of standard types.
37 | """
38 |
39 | custom_property = 512
40 |
41 |
42 | # create a VendorInfo object for this custom application before registering
43 | # specialize object classes
44 | custom_vendor_info = VendorInfo(
45 | _vendor_id, ProprietaryObjectType, ProprietaryPropertyIdentifier
46 | )
47 |
48 |
49 | class DeviceObject(_DeviceObject):
50 | """
51 | When running as an instance of this custom device, the DeviceObject is
52 | an extension of the one defined in bacpypes3.local.device (in this case
53 | doesn't add any proprietary properties).
54 | """
55 |
56 | pass
57 |
58 |
59 | class NetworkPortObject(_NetworkPortObject):
60 | """
61 | When running as an instance of this custom device, the NetworkPortObject is
62 | an extension of the one defined in bacpypes3.local.networkport (in this
63 | case doesn't add any proprietary properties).
64 | """
65 |
66 | pass
67 |
68 |
69 | class ProprietaryObject(_Object):
70 | """
71 | This is a proprietary object type.
72 | """
73 |
74 | # object identifiers are interpreted from this customized subclass of the
75 | # standard ObjectIdentifier that leverages the ProprietaryObjectType
76 | # enumeration in the vendor information
77 | objectIdentifier: custom_vendor_info.object_identifier
78 |
79 | # all objects get the object-type property to be this value
80 | objectType = ProprietaryObjectType("custom_object")
81 |
82 | # all objects have an object-name property, provided by the parent class
83 | # with special hooks if an instance of this class is bound to an application
84 | # objectName: CharacterString
85 |
86 | # the property-list property of this object is provided by the getter
87 | # method defined in the parent class and computed dynamically
88 | # propertyList: ArrayOf(PropertyIdentifier)
89 |
90 | # this is a custom property using a standard datatype
91 | custom_property: Integer
92 |
--------------------------------------------------------------------------------
/samples/device-object-init-1.py:
--------------------------------------------------------------------------------
1 | """
2 | Create a local device object instance, convert it to JSON and print the result,
3 | then round-trip the JSON into another device object.
4 | """
5 | from pprint import pprint
6 |
7 | from bacpypes3.debugging import ModuleLogger
8 |
9 | # from bacpypes3.argparse import create_log_handler
10 |
11 | from bacpypes3.local.device import DeviceObject
12 | from bacpypes3.json import sequence_to_json, json_to_sequence
13 |
14 | # some debugging
15 | _debug = 0
16 | _log = ModuleLogger(globals())
17 |
18 | # create_log_handler("__main__")
19 | # create_log_handler("bacpypes3.constructeddata", color=4)
20 | # create_log_handler("bacpypes3.object", color=5)
21 | # create_log_handler("bacpypes3.local.object", color=6)
22 | # create_log_handler("bacpypes3.local.device", color=6)
23 |
24 | do1 = DeviceObject(objectIdentifier="device,1", objectName="Excelsior")
25 | print("do1")
26 | do1.debug_contents()
27 | print("")
28 |
29 | json_content = sequence_to_json(do1)
30 | pprint(json_content)
31 | print("")
32 |
33 | do2 = json_to_sequence(json_content, DeviceObject)
34 | print("do2")
35 | do2.debug_contents()
36 | print("")
37 |
--------------------------------------------------------------------------------
/samples/device-object-init-2.py:
--------------------------------------------------------------------------------
1 | """
2 | Define a new device object for vendor identifier 888, create an instance of it,
3 | dump the contents as JSON, then round-trip the JSON back into a device object.
4 | """
5 | from pprint import pprint
6 |
7 | from bacpypes3.debugging import ModuleLogger
8 |
9 | # from bacpypes3.argparse import create_log_handler
10 |
11 | from bacpypes3.object import VendorInfo
12 | from bacpypes3.local.device import DeviceObject as _DeviceObject
13 | from bacpypes3.json import sequence_to_json, json_to_sequence
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 | # this vendor identifier reference is used when registering custom classes
20 | _vendor_id = 888
21 |
22 | # create a VendorInfo object for this custom application before registering
23 | # specialize object classes
24 | custom_vendor_info = VendorInfo(_vendor_id)
25 |
26 | # create_log_handler("__main__")
27 | # create_log_handler("bacpypes3.constructeddata", color=4)
28 | # create_log_handler("bacpypes3.object", color=5)
29 | # create_log_handler("bacpypes3.local.object", color=6)
30 | # create_log_handler("bacpypes3.local.device", color=6)
31 |
32 |
33 | class DeviceObject(_DeviceObject):
34 | """
35 | When running as an instance of this custom device, the DeviceObject is
36 | an extension of the one defined in bacpypes3.local.device (in this case
37 | doesn't add any proprietary properties).
38 |
39 | The vendor-identifier property isn't set from the module for device
40 | objects, it is simpler to provide the value here in the class definition.
41 | """
42 |
43 | vendorIdentifier = _vendor_id
44 |
45 |
46 | do1 = DeviceObject(objectIdentifier="device,1", objectName="Excelsior")
47 | print("do1")
48 | do1.debug_contents()
49 | print("")
50 |
51 | json_content = sequence_to_json(do1)
52 | pprint(json_content)
53 | print("")
54 |
55 | do2 = json_to_sequence(json_content, DeviceObject)
56 | print("do2")
57 | do2.debug_contents()
58 | print("")
59 |
--------------------------------------------------------------------------------
/samples/discover-devices.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends a Who-Is request and prints out the device identifier,
3 | address, and description for the devices that respond.
4 | """
5 |
6 | import sys
7 | import asyncio
8 |
9 | from bacpypes3.debugging import ModuleLogger
10 | from bacpypes3.argparse import SimpleArgumentParser
11 |
12 | from bacpypes3.pdu import Address
13 | from bacpypes3.primitivedata import ObjectIdentifier
14 | from bacpypes3.apdu import ErrorRejectAbortNack
15 | from bacpypes3.app import Application
16 |
17 | # some debugging
18 | _debug = 0
19 | _log = ModuleLogger(globals())
20 |
21 | # globals
22 | show_warnings: bool = False
23 |
24 |
25 | async def main() -> None:
26 | app = None
27 | try:
28 | parser = SimpleArgumentParser()
29 | parser.add_argument(
30 | "low_limit",
31 | type=int,
32 | help="device instance range low limit",
33 | )
34 | parser.add_argument(
35 | "high_limit",
36 | type=int,
37 | help="device instance range high limit",
38 | )
39 | args = parser.parse_args()
40 | if _debug:
41 | _log.debug("args: %r", args)
42 |
43 | # build an application
44 | app = Application.from_args(args)
45 | if _debug:
46 | _log.debug("app: %r", app)
47 |
48 | # run the query
49 | i_ams = await app.who_is(args.low_limit, args.high_limit)
50 | for i_am in i_ams:
51 | if _debug:
52 | _log.debug(" - i_am: %r", i_am)
53 |
54 | device_address: Address = i_am.pduSource
55 | device_identifier: ObjectIdentifier = i_am.iAmDeviceIdentifier
56 | print(f"{device_identifier} @ {device_address}")
57 |
58 | try:
59 | device_description: str = await app.read_property(
60 | device_address, device_identifier, "description"
61 | )
62 | print(f" description: {device_description}")
63 |
64 | except ErrorRejectAbortNack as err:
65 | if show_warnings:
66 | sys.stderr.write(f"{device_identifier} description error: {err}\n")
67 | finally:
68 | if app:
69 | app.close()
70 |
71 |
72 | if __name__ == "__main__":
73 | asyncio.run(main())
74 |
--------------------------------------------------------------------------------
/samples/discover-objects-rdf.ttl:
--------------------------------------------------------------------------------
1 | @prefix bacnet: .
2 | @prefix rdfs: .
3 | @prefix xsd: .
4 |
5 | a bacnet:Device ;
6 | bacnet:device-address [ rdfs:label "10.0.1.90" ;
7 | bacnet:mac-address "0a00015abac0"^^xsd:hexBinary ;
8 | bacnet:network-number "0"^^xsd:nonNegativeInteger ] ;
9 | bacnet:device-instance 999 ;
10 | bacnet:hasObject ,
11 | .
12 |
13 | bacnet:object-identifier "device,999" ;
14 | bacnet:object-name "Excelsior" ;
15 | bacnet:object-type bacnet:ObjectType.device ;
16 | bacnet:property-list "object-identifier;object-name;object-type;property-list;system-status;vendor-name;vendor-identifier;model-name;firmware-revision;application-software-version;protocol-version;protocol-revision;protocol-services-supported;protocol-object-types-supported;object-list;max-apdu-length-accepted;segmentation-supported;max-segments-accepted;local-time;local-date;apdu-segment-timeout;apdu-timeout;number-of-apdu-retries;device-address-binding;database-revision;active-cov-subscriptions;status-flags" .
17 |
18 | bacnet:object-identifier "network-port,1" ;
19 | bacnet:object-name "NetworkPort-1" ;
20 | bacnet:object-type bacnet:ObjectType.network-port ;
21 | bacnet:property-list "object-identifier;object-name;object-type;property-list;status-flags;reliability;out-of-service;network-type;protocol-level;network-number;network-number-quality;changes-pending;mac-address;link-speed;bacnet-ip-mode;ip-address;bacnet-ip-udp-port;ip-subnet-mask" .
22 |
23 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # the script needs to be run from its directory
4 | DIR=`dirname $0`
5 | pushd $DIR > /dev/null
6 |
7 | # find the latest wheel put in this directory
8 | BACPYPES_WHEEL=`ls -1 bacpypes3-*-py3-none-any.whl 2> /dev/null | tail -n 1`
9 |
10 | if [[ -z "${BACPYPES_WHEEL}" ]]
11 | then
12 | python3 -m pip download bacpypes3
13 | BACPYPES_WHEEL=`ls -1 bacpypes3-*-py3-none-any.whl | tail -n 1`
14 | fi
15 | echo Building from $BACPYPES_WHEEL
16 |
17 | # build the image passing in the file name
18 | docker build --tag bacpypes:latest \
19 | --file bacpypes.dockerfile \
20 | --build-arg BACPYPES_WHEEL=`basename $BACPYPES_WHEEL` \
21 | .
22 |
23 | popd
24 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-greetings-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # the script needs to be run from its directory
4 | DIR=`dirname $0`
5 | pushd $DIR > /dev/null
6 |
7 | # find the latest wheel put in this directory
8 | BACPYPES_WHEEL=`ls -1 bacpypes3-*-py3-none-any.whl | tail -n 1`
9 |
10 | if [[ -z "${BACPYPES_WHEEL}" ]]
11 | then
12 | echo "missing wheel"
13 | exit 1
14 | fi
15 |
16 | # build the image passing in the file name
17 | docker build --tag bacpypes-greetings \
18 | --file bacpypes-greetings.dockerfile \
19 | --build-arg BACPYPES_WHEEL=`basename $BACPYPES_WHEEL` \
20 | .
21 |
22 | popd
23 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-greetings-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker run -it --rm bacpypes-greetings
4 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-greetings-save.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker save bacpypes-greetings:latest > bacpypes-greetings.tar
4 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-greetings.dockerfile:
--------------------------------------------------------------------------------
1 | FROM bacpypes:latest
2 |
3 | WORKDIR /app
4 |
5 | COPY bacpypes-greetings.py .
6 |
7 | CMD ["python3", "bacpypes-greetings.py"]
8 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-greetings.py:
--------------------------------------------------------------------------------
1 | """
2 | Greetings from BACpypes
3 | """
4 |
5 | import bacpypes3
6 |
7 | print("Greetings from BACpypes, version", bacpypes3.__version__)
8 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ -z "x" ]]
4 | then
5 | if [[ -z "${BACPYPES_FOREIGN_BBMD}" ]]
6 | then
7 | echo "The address of the BBMD for foreign device registration"
8 | echo ""
9 | read -p "BBMD Address " BACPYPES_FOREIGN_BBMD || exit 1
10 | export BACPYPES_FOREIGN_BBMD
11 | fi
12 | if [[ -z "${BACPYPES_FOREIGN_TTL}" ]]
13 | then
14 | read -p "Time-to-live " BACPYPES_FOREIGN_TTL || exit 1
15 | export BACPYPES_FOREIGN_TTL
16 | fi
17 | fi
18 |
19 | docker run -it --rm \
20 | --network host \
21 | --env BACPYPES_DEVICE_ADDRESS \
22 | --env BACPYPES_DEVICE_NAME \
23 | --env BACPYPES_DEVICE_INSTANCE \
24 | --env BACPYPES_NETWORK \
25 | --env BACPYPES_VENDOR_IDENTIFIER \
26 | --env BACPYPES_FOREIGN_BBMD \
27 | --env BACPYPES_FOREIGN_TTL \
28 | --env BACPYPES_BBMD_BDT \
29 | --env BACPYPES_DEBUG \
30 | bacpypes:latest
31 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes-save.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker save bacpypes:latest > bacpypes.tar
4 |
--------------------------------------------------------------------------------
/samples/docker/bacpypes.dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10-slim
2 |
3 | WORKDIR /app
4 | RUN pip install --root-user-action=ignore --upgrade pip
5 |
6 | COPY requirements.txt .
7 | RUN pip install --root-user-action=ignore -r requirements.txt
8 |
9 | ARG BACPYPES_WHEEL
10 | COPY ${BACPYPES_WHEEL} .
11 | RUN pip install --root-user-action=ignore ${BACPYPES_WHEEL}
12 |
13 | CMD ["python3", "-m", "bacpypes3"]
14 |
15 |
--------------------------------------------------------------------------------
/samples/docker/requirements.txt:
--------------------------------------------------------------------------------
1 | ifaddr
2 | pyyaml
3 | rdflib
4 | websockets
5 |
--------------------------------------------------------------------------------
/samples/docker/who-is-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # the script needs to be run from its directory
4 | DIR=`dirname $0`
5 | pushd $DIR > /dev/null
6 |
7 | docker build --tag who-is:latest \
8 | --file who-is.dockerfile \
9 | .
10 |
11 | popd
12 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # the script needs to be run from its directory
4 | DIR=`dirname $0`
5 | pushd $DIR > /dev/null
6 |
7 | docker build \
8 | --tag who-is-console:latest \
9 | --file who-is-console.dockerfile \
10 | .
11 |
12 | popd
13 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console-run-mini.sh:
--------------------------------------------------------------------------------
1 | export HOST_PORT=47808
2 | export BBMD_ADDRESS=10.0.1.90
3 | export TTL=30
4 | export DEBUG="--debug bacpypes3.ipv4.IPv4DatagramServer bacpypes3.ipv4.service.BIPForeign --color"
5 |
6 | docker run \
7 | -it \
8 | --rm \
9 | -p $HOST_PORT:47808/udp \
10 | --env BBMD_ADDRESS \
11 | --env TTL \
12 | --env DEBUG \
13 | who-is-console:latest
14 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker run -it --rm \
4 | --network host \
5 | who-is-console:latest
6 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console-save.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker save who-is-console:latest > who-is-console.tar
4 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console.dockerfile:
--------------------------------------------------------------------------------
1 | FROM bacpypes:latest
2 |
3 | COPY who-is-console.py .
4 |
5 | CMD ["python3", "who-is-console.py"]
6 |
7 |
--------------------------------------------------------------------------------
/samples/docker/who-is-console.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends Who-Is requests.
3 | """
4 |
5 | import asyncio
6 | from typing import Callable
7 |
8 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
9 | from bacpypes3.argparse import SimpleArgumentParser
10 | from bacpypes3.app import Application
11 | from bacpypes3.console import Console
12 | from bacpypes3.cmd import Cmd
13 |
14 | from bacpypes3.pdu import Address
15 | from bacpypes3.comm import bind
16 |
17 | # some debugging
18 | _debug = 0
19 | _log = ModuleLogger(globals())
20 |
21 |
22 | @bacpypes_debugging
23 | class SampleCmd(Cmd):
24 | """
25 | Simple console example that sends Who-Is requests.
26 | """
27 |
28 | _debug: Callable[..., None]
29 |
30 | async def do_whois(
31 | self,
32 | address: Address = None,
33 | low_limit: int = None,
34 | high_limit: int = None,
35 | ) -> None:
36 | """
37 | Send a Who-Is request and wait for the responses.
38 |
39 | usage: whois [ address [ low_limit high_limit ] ]
40 | """
41 | if _debug:
42 | SampleCmd._debug("do_whois %r %r %r", address, low_limit, high_limit)
43 |
44 | i_ams = await this_application.who_is(low_limit, high_limit, address)
45 | if not i_ams:
46 | await self.response("No responses")
47 | else:
48 | for i_am in i_ams:
49 | if _debug:
50 | SampleCmd._debug(" - i_am: %r", i_am)
51 | await self.response(f"{i_am.iAmDeviceIdentifier[1]} {i_am.pduSource}")
52 |
53 |
54 | async def main() -> None:
55 | global this_application
56 |
57 | this_application = None
58 | try:
59 | parser = SimpleArgumentParser()
60 | args = parser.parse_args()
61 | if _debug:
62 | _log.debug("args: %r", args)
63 |
64 | # build a very small stack
65 | console = Console()
66 | cmd = SampleCmd()
67 | bind(console, cmd)
68 |
69 | # build an application
70 | this_application = Application.from_args(args)
71 | if _debug:
72 | _log.debug("this_application: %r", this_application)
73 |
74 | # wait until the user is done
75 | await console.fini.wait()
76 |
77 | except KeyboardInterrupt:
78 | if _debug:
79 | _log.debug("keyboard interrupt")
80 | finally:
81 | if this_application:
82 | this_application.close()
83 |
84 |
85 | if __name__ == "__main__":
86 | asyncio.run(main())
87 |
--------------------------------------------------------------------------------
/samples/docker/who-is-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ -z "${LOW_LIMIT}" ]]
4 | then
5 | read -p "Low limit " LOW_LIMIT || exit 1
6 | export LOW_LIMIT
7 | fi
8 | if [[ -z "${HIGH_LIMIT}" ]]
9 | then
10 | read -p "High limit " HIGH_LIMIT || exit 1
11 | export HIGH_LIMIT
12 | fi
13 |
14 | docker run -it --rm \
15 | --network host \
16 | --env LOW_LIMIT \
17 | --env HIGH_LIMIT \
18 | who-is:latest
19 |
--------------------------------------------------------------------------------
/samples/docker/who-is-save.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker save who-is:latest > who-is.tar
4 |
--------------------------------------------------------------------------------
/samples/docker/who-is.dockerfile:
--------------------------------------------------------------------------------
1 | FROM bacpypes:latest
2 |
3 | WORKDIR /app
4 |
5 | COPY who-is.py .
6 |
7 | ARG LOW_LIMIT
8 | ARG HIGH_LIMIT
9 |
10 | CMD python3 who-is.py $LOW_LIMIT $HIGH_LIMIT
11 |
12 |
--------------------------------------------------------------------------------
/samples/docker/who-is.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends a Who-Is request and prints out the device identifier
3 | and address for the devices that respond.
4 | """
5 |
6 | import asyncio
7 |
8 | from bacpypes3.debugging import ModuleLogger
9 | from bacpypes3.argparse import SimpleArgumentParser
10 | from bacpypes3.app import Application
11 |
12 | # some debugging
13 | _debug = 0
14 | _log = ModuleLogger(globals())
15 |
16 |
17 | async def main() -> None:
18 | app = None
19 | try:
20 | parser = SimpleArgumentParser()
21 | parser.add_argument(
22 | "low_limit",
23 | type=int,
24 | help="device instance range low limit",
25 | )
26 | parser.add_argument(
27 | "high_limit",
28 | type=int,
29 | help="device instance range high limit",
30 | )
31 | args = parser.parse_args()
32 | if _debug:
33 | _log.debug("args: %r", args)
34 |
35 | # build an application
36 | app = Application.from_args(args)
37 | if _debug:
38 | _log.debug("app: %r", app)
39 |
40 | # run the query
41 | i_ams = await app.who_is(args.low_limit, args.high_limit)
42 | for i_am in i_ams:
43 | if _debug:
44 | _log._debug(" - i_am: %r", i_am)
45 | print(f"{i_am.iAmDeviceIdentifier[1]} {i_am.pduSource}")
46 |
47 | finally:
48 | if app:
49 | app.close()
50 |
51 |
52 | if __name__ == "__main__":
53 | asyncio.run(main())
54 |
--------------------------------------------------------------------------------
/samples/find-device-by-address.py:
--------------------------------------------------------------------------------
1 | """
2 | This example uses a prepared query to find all of the BACnet
3 | devices in a graph, optionally with an address. By specifying
4 | different initial bindings then different collections of
5 | devices can be found.
6 | """
7 | import sys
8 | from rdflib import Graph
9 |
10 | from bacpypes3.pdu import Address
11 | from bacpypes3.rdf.core import find_device_by_address
12 | from bacpypes3.rdf.util import unsigned_encode, octetstring_encode
13 |
14 | g = Graph()
15 | g.parse(sys.argv[1])
16 |
17 | init_bindings = {}
18 | if len(sys.argv) > 2:
19 | device_address = Address(sys.argv[2])
20 |
21 | if device_address.is_localbroadcast:
22 | init_bindings["net"] = unsigned_encode(g, 0)
23 | elif device_address.is_remotestation or device_address.is_remotebroadcast:
24 | init_bindings["net"] = unsigned_encode(g, device_address.addrNet)
25 | if device_address.is_localstation or device_address.is_remotestation:
26 | init_bindings["addr"] = octetstring_encode(g, device_address.addrAddr)
27 |
28 | for row in g.query(find_device_by_address, initBindings=init_bindings):
29 | print(row[0])
30 |
--------------------------------------------------------------------------------
/samples/find-device-by-instance.py:
--------------------------------------------------------------------------------
1 | """
2 | This example uses a prepared query to find all of the BACnet
3 | devices in a graph, optionally with a specific device instance
4 | number.
5 | """
6 | import sys
7 | from rdflib import Graph, Literal
8 | from bacpypes3.rdf.core import find_device_by_instance
9 |
10 |
11 | g = Graph()
12 | g.parse(sys.argv[1])
13 |
14 | init_bindings = {}
15 | if len(sys.argv) > 2:
16 | init_bindings["device_instance"] = Literal(int(sys.argv[2]))
17 |
18 | for row in g.query(find_device_by_instance, initBindings=init_bindings):
19 | print(row[0])
20 |
--------------------------------------------------------------------------------
/samples/ipv4-to-ipv4.json:
--------------------------------------------------------------------------------
1 | {
2 | "BACpypes": {
3 | "backup_count": 5,
4 | "color": false,
5 | "config": {},
6 | "cov_lifetime": 60,
7 | "debug": [],
8 | "debug_file": "",
9 | "max_bytes": 1048576,
10 | "route_aware": false
11 | },
12 | "router": [
13 | {
14 | "apdu-segment-timeout": 1000,
15 | "apdu-timeout": 3000,
16 | "application-software-version": "1.0",
17 | "database-revision": 1,
18 | "firmware-revision": "N/A",
19 | "max-apdu-length-accepted": 1024,
20 | "model-name": "N/A",
21 | "number-of-apdu-retries": 3,
22 | "object-identifier": "device,998",
23 | "object-name": "Exeter",
24 | "object-type": "device",
25 | "protocol-revision": 22,
26 | "protocol-version": 1,
27 | "segmentation-supported": "segmented-both",
28 | "system-status": "operational",
29 | "vendor-identifier": 999,
30 | "vendor-name": "BACpypes"
31 | },
32 | {
33 | "bacnet-ip-mode": "normal",
34 | "bacnet-ip-udp-port": 47808,
35 | "changes-pending": false,
36 | "ip-address": "10.0.1.90",
37 | "ip-subnet-mask": "255.255.255.0",
38 | "link-speed": 0.0,
39 | "mac-address": "10.0.1.90:47808",
40 | "network-number": 100,
41 | "network-number-quality": "configured",
42 | "network-type": "ipv4",
43 | "object-identifier": "network-port,1",
44 | "object-name": "NetworkPort-1",
45 | "object-type": "network-port",
46 | "out-of-service": false,
47 | "protocol-level": "bacnet-application",
48 | "reliability": "no-fault-detected"
49 | },
50 | {
51 | "bacnet-ip-mode": "normal",
52 | "bacnet-ip-udp-port": 47809,
53 | "changes-pending": false,
54 | "ip-address": "10.0.1.90",
55 | "ip-subnet-mask": "255.255.255.0",
56 | "link-speed": 0.0,
57 | "mac-address": "10.0.1.90:47809",
58 | "network-number": 200,
59 | "network-number-quality": "configured",
60 | "network-type": "ipv4",
61 | "object-identifier": "network-port,2",
62 | "object-name": "NetworkPort-2",
63 | "object-type": "network-port",
64 | "out-of-service": false,
65 | "protocol-level": "bacnet-application",
66 | "reliability": "no-fault-detected"
67 | }
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/samples/link-layer/sc-echo-client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that sends websocket messages.
5 | """
6 |
7 | import sys
8 | import asyncio
9 | import logging
10 |
11 | from typing import Callable
12 |
13 | from bacpypes3.settings import settings
14 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
15 | from bacpypes3.argparse import ArgumentParser
16 | from bacpypes3.console import Console
17 | from bacpypes3.cmd import Cmd
18 |
19 | from bacpypes3.comm import Client, bind
20 | from bacpypes3.pdu import PDU
21 | from bacpypes3.sc.service import SCNodeSwitch
22 |
23 | # some debugging
24 | _debug = 0
25 | _log = ModuleLogger(globals())
26 |
27 | direct_connection = None
28 |
29 |
30 | @bacpypes_debugging
31 | class SampleCmd(Cmd, Client[PDU]):
32 | """
33 | Sample Cmd
34 | """
35 |
36 | _debug: Callable[..., None]
37 |
38 | async def do_send(self, data: str) -> None:
39 | """
40 | usage: send data:str
41 | """
42 | if _debug:
43 | SampleCmd._debug("do_send %r", data)
44 | global direct_connection
45 |
46 | pdu = PDU(data.encode(), destination=direct_connection)
47 | if _debug:
48 | SampleCmd._debug(" - pdu: %r", pdu)
49 |
50 | await self.request(pdu)
51 |
52 | async def confirmation(self, pdu: PDU) -> None:
53 | if _debug:
54 | SampleCmd._debug("confirmation %r", pdu)
55 |
56 | await self.response(pdu.pduData.decode())
57 |
58 |
59 | async def main() -> None:
60 | global direct_connection
61 |
62 | switch = None
63 | console = None
64 | try:
65 | parser = ArgumentParser()
66 | parser.add_argument(
67 | "--host",
68 | type=str,
69 | default="localhost",
70 | help="listening host address",
71 | )
72 | parser.add_argument(
73 | "--port",
74 | type=int,
75 | default=8765,
76 | help="listening port",
77 | )
78 | args = parser.parse_args()
79 | if _debug:
80 | _log.debug("settings: %r", settings)
81 | _log.debug("args: %r", args)
82 |
83 | # build a very small stack
84 | console = Console()
85 | cmd = SampleCmd()
86 | switch = SCNodeSwitch()
87 | bind(console, cmd, switch) # type: ignore[misc]
88 |
89 | # establish a direct connection
90 | direct_connection = switch.connect_to_device(f"ws://{args.host}:{args.port}")
91 | if _debug:
92 | _log.debug("direct_connection: %r", direct_connection)
93 |
94 | # run until the console is done, canceled or EOF
95 | await console.fini.wait()
96 | _log.debug("console fini")
97 |
98 | except KeyboardInterrupt:
99 | if _debug:
100 | _log.debug("keyboard interrupt")
101 | finally:
102 | if direct_connection:
103 | _log.debug("direct_connection.close()")
104 | await direct_connection.close()
105 | if switch:
106 | _log.debug("switch.close()")
107 | await switch.close()
108 | if console and console.exit_status:
109 | _log.debug("sys.exit()")
110 | sys.exit(console.exit_status)
111 |
112 |
113 | if __name__ == "__main__":
114 | asyncio.run(main())
115 |
--------------------------------------------------------------------------------
/samples/link-layer/sc-echo-server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos websocket messages, upper cased.
5 | """
6 |
7 | import asyncio
8 | import logging
9 |
10 | from typing import Callable
11 |
12 | from bacpypes3.settings import settings
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 |
16 | from bacpypes3.comm import Client, bind
17 | from bacpypes3.pdu import PDU
18 | from bacpypes3.sc.service import SCNodeSwitch
19 |
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class Echo(Client[PDU]):
28 | """
29 | Echo
30 | """
31 |
32 | _debug: Callable[..., None]
33 |
34 | async def confirmation(self, pdu: PDU) -> None:
35 | if _debug:
36 | Echo._debug("confirmation %r", pdu)
37 |
38 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
39 | if _debug:
40 | Echo._debug(" - ack: %r", ack)
41 |
42 | await self.request(ack)
43 |
44 |
45 | async def main() -> None:
46 | switch = None
47 |
48 | try:
49 | parser = ArgumentParser()
50 | parser.add_argument(
51 | "--host",
52 | type=str,
53 | default="localhost",
54 | help="listening host address",
55 | )
56 | parser.add_argument(
57 | "--port",
58 | type=int,
59 | default=8765,
60 | help="listening port",
61 | )
62 | args = parser.parse_args()
63 | if _debug:
64 | _log.debug("settings: %r", settings)
65 |
66 | # build a very small stack
67 | echo = Echo()
68 | switch = SCNodeSwitch(
69 | host=args.host, port=args.port, dc_support=True, hub_support=True
70 | )
71 | bind(echo, switch)
72 |
73 | # run a really long time
74 | await asyncio.Future()
75 |
76 | except KeyboardInterrupt:
77 | if _debug:
78 | _log.debug("keyboard interrupt")
79 | finally:
80 | if switch:
81 | await switch.close()
82 |
83 |
84 | if __name__ == "__main__":
85 | try:
86 | asyncio.run(main())
87 | except KeyboardInterrupt:
88 | pass
89 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv4-echo-client-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker build \
4 | --tag bacpypes3-udp-ipv4-echo-client:latest \
5 | --file udp-ipv4-echo-client.dockerfile .
6 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv4-echo-client-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ -z "${HOST_ADDRESS}" ]]
4 | then
5 | echo "The HOST_ADDRESS is the UDP port number of the docker host"
6 | echo "like '47808' or an IPv4 address and port number if the host"
7 | echo "has more than one home, like '10.0.1.70:47808' and may also"
8 | echo "have an alternative port number like '10.0.1.99:47809'"
9 | echo ""
10 | read -p "Host Address " HOST_ADDRESS || exit 1
11 | export HOST_ADDRESS
12 | echo ""
13 | fi
14 | if [[ -z "${LOCAL_PORT}" ]]
15 | then
16 | echo "The LOCAL_PORT is the UDP port number the application"
17 | echo "uses on the docker network, normally '47808'. The"
18 | echo "IPv4 address is assigned by docker"
19 | echo ""
20 | read -p "Local Port " LOCAL_PORT || exit 1
21 | export LOCAL_PORT
22 | echo ""
23 | fi
24 | if [[ -z "${DEBUG}" ]]
25 | then
26 | export DEBUG=""
27 | fi
28 |
29 | docker run \
30 | -it \
31 | --rm \
32 | -p $HOST_ADDRESS:${LOCAL_PORT} \
33 | --env LOCAL_PORT \
34 | --env DEBUG \
35 | bacpypes3-udp-ipv4-echo-client:latest
36 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv4-echo-client.dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-slim-buster
2 |
3 | WORKDIR /app
4 |
5 | RUN python3 -m pip install --upgrade pip
6 | RUN python3 -m pip install pipenv
7 | RUN pipenv --python 3.8
8 | RUN pipenv run python3 -m pip install --upgrade --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ bacpypes3
9 |
10 | COPY udp-ipv4-echo-client.py .
11 |
12 | ARG LOCAL_PORT
13 | ARG DEBUG=
14 |
15 | CMD pipenv run python3 udp-ipv4-echo-client.py host:${LOCAL_PORT} ${DEBUG}
16 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv4-echo-client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that sends IPv4 UDP messages.
5 | """
6 |
7 | import sys
8 | import asyncio
9 |
10 | from typing import Callable
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.argparse import ArgumentParser
14 | from bacpypes3.console import Console
15 | from bacpypes3.cmd import Cmd
16 |
17 | from bacpypes3.comm import Client, bind
18 | from bacpypes3.pdu import Address, LocalBroadcast, IPv4Address, PDU
19 | from bacpypes3.ipv4 import IPv4DatagramServer
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class SampleCmd(Cmd, Client[PDU]):
28 | """
29 | Sample Cmd
30 | """
31 |
32 | _debug: Callable[..., None]
33 |
34 | async def do_send(self, address: Address, data: str) -> None:
35 | """
36 | usage: send address:Address data:str
37 | """
38 | if _debug:
39 | SampleCmd._debug("do_send %r %r", address, data)
40 |
41 | if not isinstance(address, (IPv4Address, LocalBroadcast)):
42 | if _debug:
43 | SampleCmd._debug(" - invalid address: %r", address)
44 | await self.response("invalid address: " + str(address))
45 | return
46 |
47 | pdu = PDU(data.encode(), destination=address)
48 | if _debug:
49 | SampleCmd._debug(" - pdu: %r", pdu)
50 |
51 | await self.request(pdu)
52 |
53 | async def confirmation(self, pdu: PDU) -> None:
54 | if _debug:
55 | SampleCmd._debug("confirmation %r", pdu)
56 |
57 | await self.response(str(pdu.pduData.decode()))
58 |
59 |
60 | async def main() -> None:
61 | try:
62 | console = cmd = server = None
63 | parser = ArgumentParser()
64 | parser.add_argument(
65 | "local_address",
66 | type=str,
67 | help="local address (e.g., 'host:47808')",
68 | )
69 |
70 | args = parser.parse_args()
71 | if _debug:
72 | _log.debug("args: %r", args)
73 |
74 | # evaluate the address
75 | local_address = IPv4Address(args.local_address)
76 | if _debug:
77 | _log.debug("local_address: %r", local_address)
78 |
79 | # build a very small stack
80 | server = IPv4DatagramServer(local_address)
81 | cmd = SampleCmd()
82 | console = Console()
83 | bind(console, cmd, server) # type: ignore[misc]
84 |
85 | # run until the console is done, canceled or EOF
86 | await console.fini.wait()
87 |
88 | except KeyboardInterrupt:
89 | if _debug:
90 | _log.debug("keyboard interrupt")
91 | finally:
92 | if server:
93 | server.close()
94 | if console and console.exit_status:
95 | sys.exit(console.exit_status)
96 |
97 |
98 | if __name__ == "__main__":
99 | asyncio.run(main())
100 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv4-echo-server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos IPv4 UDP messages, upper cased.
5 | """
6 |
7 | import asyncio
8 |
9 | from typing import Callable
10 |
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.argparse import ArgumentParser
13 |
14 | from bacpypes3.comm import Client, bind
15 | from bacpypes3.pdu import IPv4Address, PDU
16 | from bacpypes3.ipv4 import IPv4DatagramServer
17 |
18 |
19 | # some debugging
20 | _debug = 0
21 | _log = ModuleLogger(globals())
22 |
23 |
24 | @bacpypes_debugging
25 | class Echo(Client[PDU]):
26 | """
27 | Echo
28 | """
29 |
30 | _debug: Callable[..., None]
31 |
32 | async def confirmation(self, pdu: PDU) -> None:
33 | if _debug:
34 | Echo._debug("confirmation %r", pdu)
35 |
36 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
37 | if _debug:
38 | Echo._debug(" - ack: %r", ack)
39 |
40 | await self.request(ack)
41 |
42 |
43 | async def main() -> None:
44 | try:
45 | echo = server = None
46 | parser = ArgumentParser()
47 | parser.add_argument(
48 | "local_address",
49 | type=str,
50 | # nargs="?",
51 | help="local address",
52 | )
53 |
54 | args = parser.parse_args()
55 | if _debug:
56 | _log.debug("args: %r", args)
57 |
58 | # evaluate the address
59 | local_address = IPv4Address(args.local_address)
60 | if _debug:
61 | _log.debug("local_address: %r", local_address)
62 |
63 | # build a very small stack
64 | echo = Echo()
65 | server = IPv4DatagramServer(local_address)
66 | bind(echo, server)
67 |
68 | # run a really long time
69 | await asyncio.Future()
70 |
71 | except KeyboardInterrupt:
72 | if _debug:
73 | _log.debug("keyboard interrupt")
74 | finally:
75 | if server:
76 | server.close()
77 |
78 |
79 | if __name__ == "__main__":
80 | asyncio.run(main())
81 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv6-echo-client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that sends IPv6 UDP messages.
5 | """
6 |
7 | import sys
8 | import asyncio
9 |
10 | from typing import Callable
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.argparse import ArgumentParser
14 | from bacpypes3.console import Console
15 | from bacpypes3.cmd import Cmd
16 |
17 | from bacpypes3.comm import Client, bind
18 | from bacpypes3.pdu import Address, LocalBroadcast, IPv6Address, PDU
19 | from bacpypes3.ipv6 import IPv6DatagramServer
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class SampleCmd(Cmd, Client[PDU]):
28 | """
29 | Sample Cmd
30 | """
31 |
32 | _debug: Callable[..., None]
33 |
34 | async def do_send(self, address: Address, data: str) -> None:
35 | """
36 | usage: send address:IPv6Address data:str
37 | """
38 | if _debug:
39 | SampleCmd._debug("do_send %r %r", address, data)
40 |
41 | if not isinstance(address, (IPv6Address, LocalBroadcast)):
42 | if _debug:
43 | SampleCmd._debug(" - invalid address: %r", address)
44 | await self.response("invalid address: " + str(address))
45 | return
46 |
47 | pdu = PDU(data.encode(), destination=address)
48 | if _debug:
49 | SampleCmd._debug(" - pdu: %r", pdu)
50 |
51 | await self.request(pdu)
52 |
53 | async def confirmation(self, pdu: PDU) -> None:
54 | if _debug:
55 | SampleCmd._debug("confirmation %r", pdu)
56 |
57 | await self.response(str(pdu.pduData.decode()))
58 |
59 |
60 | async def main() -> None:
61 | try:
62 | console = server = None
63 | parser = ArgumentParser()
64 | parser.add_argument(
65 | "address",
66 | type=str,
67 | nargs="?",
68 | default="[::]",
69 | help="listening address (default [::])",
70 | )
71 |
72 | args = parser.parse_args()
73 | if _debug:
74 | _log.debug("args: %r", args)
75 |
76 | # evaluate the address
77 | address = IPv6Address(args.address)
78 | if _debug:
79 | _log.debug("address: %r", address)
80 |
81 | # build a very small stack
82 | server = IPv6DatagramServer(address)
83 | cmd = SampleCmd()
84 | console = Console()
85 | bind(console, cmd, server) # type: ignore[misc]
86 |
87 | # run until the console is done, canceled or EOF
88 | await console.fini.wait()
89 |
90 | except KeyboardInterrupt:
91 | if _debug:
92 | _log.debug("keyboard interrupt")
93 | finally:
94 | if server:
95 | server.close()
96 | if console and console.exit_status:
97 | sys.exit(console.exit_status)
98 |
99 |
100 | if __name__ == "__main__":
101 | asyncio.run(main())
102 |
--------------------------------------------------------------------------------
/samples/link-layer/udp-ipv6-echo-server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple console example that echos IPv6 UDP messages, upper cased. Given this
5 | IP configuration:
6 |
7 | $ ip addr
8 | 2: enp0s25: mtu 1500 ...
9 | link/ether 00:23:ae:95:c2:82 brd ff:ff:ff:ff:ff:ff
10 | inet 10.0.1.90/24 brd 10.0.1.255 scope global dynamic noprefixroute enp0s25
11 | valid_lft 82093sec preferred_lft 82093sec
12 | inet6 fe80::1b99:de63:cdd6:67a9/64 scope link noprefixroute
13 | valid_lft forever preferred_lft forever
14 |
15 | Examples:
16 |
17 | $ python3 samples/udp-ipv6-echo-server.py [::]:47809
18 | $ python3 samples/udp-ipv6-echo-server.py [fe80::1b99:de63:cdd6:67a9/64]:47809%enp0s25
19 | """
20 |
21 | import asyncio
22 |
23 | from typing import Callable
24 |
25 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
26 | from bacpypes3.argparse import ArgumentParser
27 |
28 | from bacpypes3.comm import Client, bind
29 | from bacpypes3.pdu import IPv6Address, PDU
30 | from bacpypes3.ipv6 import IPv6DatagramServer
31 |
32 | # some debugging
33 | _debug = 0
34 | _log = ModuleLogger(globals())
35 |
36 |
37 | @bacpypes_debugging
38 | class Echo(Client[PDU]):
39 | """
40 | Echo
41 | """
42 |
43 | _debug: Callable[..., None]
44 |
45 | async def confirmation(self, pdu: PDU) -> None:
46 | if _debug:
47 | Echo._debug("confirmation %r", pdu)
48 |
49 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
50 | if _debug:
51 | Echo._debug(" - ack: %r", ack)
52 |
53 | await self.request(ack)
54 |
55 |
56 | async def main() -> None:
57 | try:
58 | server = None
59 | parser = ArgumentParser()
60 | parser.add_argument(
61 | "address",
62 | type=str,
63 | nargs="?",
64 | default="[::]",
65 | help="listening address (default [::])",
66 | )
67 |
68 | args = parser.parse_args()
69 | if _debug:
70 | _log.debug("args: %r", args)
71 |
72 | # evaluate the address
73 | address = IPv6Address(args.address)
74 | if _debug:
75 | _log.debug("address: %r", address)
76 |
77 | # build a very small stack
78 | echo = Echo()
79 | server = IPv6DatagramServer(address)
80 | bind(echo, server)
81 |
82 | # run a really long time
83 | await asyncio.Future()
84 |
85 | except KeyboardInterrupt:
86 | if _debug:
87 | _log.debug("keyboard interrupt")
88 | finally:
89 | if server:
90 | server.close()
91 |
92 |
93 | if __name__ == "__main__":
94 | asyncio.run(main())
95 |
--------------------------------------------------------------------------------
/samples/multiple-stacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "BACpypes": {
3 | "backup_count": 5,
4 | "color": false,
5 | "config": {},
6 | "cov_lifetime": 60,
7 | "debug": [],
8 | "debug_file": "",
9 | "max_bytes": 1048576,
10 | "route_aware": false
11 | },
12 | "applications": [
13 | [
14 | {
15 | "apdu-segment-timeout": 1000,
16 | "apdu-timeout": 3000,
17 | "application-software-version": "1.0",
18 | "database-revision": 1,
19 | "firmware-revision": "N/A",
20 | "max-apdu-length-accepted": 1024,
21 | "model-name": "N/A",
22 | "number-of-apdu-retries": 3,
23 | "object-identifier": "device,998",
24 | "object-name": "Exeter-0",
25 | "object-type": "device",
26 | "protocol-revision": 22,
27 | "protocol-version": 1,
28 | "segmentation-supported": "segmented-both",
29 | "system-status": "operational",
30 | "vendor-identifier": 999,
31 | "vendor-name": "BACpypes"
32 | },
33 | {
34 | "bacnet-ip-mode": "normal",
35 | "bacnet-ip-udp-port": 47808,
36 | "changes-pending": false,
37 | "ip-address": "10.0.1.90",
38 | "ip-subnet-mask": "255.255.255.0",
39 | "link-speed": 0.0,
40 | "mac-address": "10.0.1.90:47808",
41 | "network-number": 100,
42 | "network-number-quality": "configured",
43 | "network-type": "ipv4",
44 | "object-identifier": "network-port,1",
45 | "object-name": "NetworkPort-1",
46 | "object-type": "network-port",
47 | "out-of-service": false,
48 | "protocol-level": "bacnet-application",
49 | "reliability": "no-fault-detected"
50 | }
51 | ],
52 | [
53 | {
54 | "apdu-segment-timeout": 1000,
55 | "apdu-timeout": 3000,
56 | "application-software-version": "1.0",
57 | "database-revision": 1,
58 | "firmware-revision": "N/A",
59 | "max-apdu-length-accepted": 1024,
60 | "model-name": "N/A",
61 | "number-of-apdu-retries": 3,
62 | "object-identifier": "device,999",
63 | "object-name": "Exeter-1",
64 | "object-type": "device",
65 | "protocol-revision": 22,
66 | "protocol-version": 1,
67 | "segmentation-supported": "segmented-both",
68 | "system-status": "operational",
69 | "vendor-identifier": 999,
70 | "vendor-name": "BACpypes"
71 | },
72 | {
73 | "bacnet-ip-mode": "normal",
74 | "bacnet-ip-udp-port": 47809,
75 | "changes-pending": false,
76 | "ip-address": "10.0.1.90",
77 | "ip-subnet-mask": "255.255.255.0",
78 | "link-speed": 0.0,
79 | "mac-address": "10.0.1.90:47809",
80 | "network-number": 200,
81 | "network-number-quality": "configured",
82 | "network-type": "ipv4",
83 | "object-identifier": "network-port,1",
84 | "object-name": "NetworkPort-1",
85 | "object-type": "network-port",
86 | "out-of-service": false,
87 | "protocol-level": "bacnet-application",
88 | "reliability": "no-fault-detected"
89 | }
90 | ]
91 | ]
92 | }
93 |
--------------------------------------------------------------------------------
/samples/network-port-1.py:
--------------------------------------------------------------------------------
1 | """
2 | Create a network port object from an address, print some interesting stuff,
3 | convert it to JSON and YAML. Variations of this sample are used to check the
4 | results of various patterns of addresses.
5 | """
6 | from pprint import pprint
7 | import yaml
8 |
9 | # from bacpypes3.argparse import create_log_handler
10 | from bacpypes3.local.networkport import NetworkPortObject
11 | from bacpypes3.json import sequence_to_json
12 |
13 | # create_log_handler("bacpypes3.object")
14 | # create_log_handler("bacpypes3.local.networkport")
15 |
16 | npo = NetworkPortObject(
17 | "5:192.168.0.99/24",
18 | objectName="Network Port 1",
19 | objectIdentifier=("network-port", 1),
20 | )
21 |
22 | print("npo:", npo)
23 | npo.debug_contents()
24 | print("")
25 |
26 | npo_address = npo.address
27 | print(f"{npo_address = }")
28 | print(f"{npo_address.network = }")
29 | print("")
30 |
31 | npo_json = sequence_to_json(npo)
32 | pprint(npo_json)
33 | print("")
34 |
35 | print(yaml.dump({"application": [npo_json]}))
36 |
--------------------------------------------------------------------------------
/samples/read-batch-point-list.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that reads batches of values with the point list
3 | coded into the application. See the read-batch.py sample for a version that
4 | gets the point list from tab-delimited text input.
5 |
6 | This version waits for a 'read' command in the console to start the batch, or
7 | 'stop' if it's already running.
8 | """
9 |
10 | import sys
11 | import asyncio
12 |
13 | from typing import Callable
14 |
15 | from bacpypes3.settings import settings
16 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
17 | from bacpypes3.argparse import SimpleArgumentParser
18 | from bacpypes3.app import Application
19 |
20 | from bacpypes3.console import Console
21 | from bacpypes3.cmd import Cmd
22 | from bacpypes3.comm import bind
23 |
24 | from bacpypes3.lib.batchread import DeviceAddressObjectPropertyReference, BatchRead
25 |
26 | # some debugging
27 | _debug = 0
28 | _log = ModuleLogger(globals())
29 |
30 | # globals
31 | app: Application
32 | batch_read: BatchRead
33 |
34 | # stuff to read, parameters to DeviceAddressObjectPropertyReference
35 | stuff_to_read = [
36 | (1, "100:2", "device,10002", "object-name"),
37 | (2, "100:2", "device,10002", "local-date"),
38 | (3, "100:2", "device,10002", "local-time"),
39 | (4, "100:2", "analog-value,1", "object-name"),
40 | (5, "100:2", "analog-value,1", "present-value"),
41 | (6, "100:3", "device,10003", "object-name"),
42 | (7, "100:4", "device,10004", "object-name"),
43 | (8, "100:5", "device,10005", "object-name"),
44 | (9, "200:2", "device,20002", "object-name"),
45 | (10, "200:2", "analog-value,1", "present-value"),
46 | (11, "200:2", "analog-value,2", "present-value"),
47 | (12, "200:3", "device:20003", "object-name"),
48 | ]
49 |
50 |
51 | @bacpypes_debugging
52 | class SampleCmd(Cmd):
53 | """
54 | Sample Cmd
55 | """
56 |
57 | _debug: Callable[..., None]
58 |
59 | async def do_read(self) -> None:
60 | """
61 | usage: read
62 | """
63 | if _debug:
64 | SampleCmd._debug("do_read")
65 |
66 | asyncio.ensure_future(batch_read.run(app, self.callback))
67 |
68 | def callback(self, key, value) -> None:
69 | asyncio.ensure_future(self.response(f"{key} = {value}"))
70 |
71 | async def do_stop(self) -> None:
72 | """
73 | usage: stop
74 | """
75 | global batch_read
76 | batch_read.stop()
77 |
78 |
79 | async def main() -> None:
80 | global app, batch_read
81 |
82 | try:
83 | app = None
84 | args = SimpleArgumentParser().parse_args()
85 | if _debug:
86 | _log.debug("settings: %r", settings)
87 |
88 | # build a very small stack
89 | console = Console()
90 | cmd = SampleCmd()
91 | bind(console, cmd)
92 |
93 | # build the application
94 | app = Application.from_args(args)
95 |
96 | # transform the list of stuff to read
97 | daopr_list = [
98 | DeviceAddressObjectPropertyReference(*parms) for parms in stuff_to_read
99 | ]
100 | batch_read = BatchRead(daopr_list)
101 |
102 | # run until the console is done, canceled or EOF
103 | await console.fini.wait()
104 |
105 | except KeyboardInterrupt:
106 | if _debug:
107 | _log.debug("keyboard interrupt")
108 | finally:
109 | if app:
110 | app.close()
111 | if console and console.exit_status:
112 | sys.exit(console.exit_status)
113 |
114 |
115 | if __name__ == "__main__":
116 | asyncio.run(main())
117 |
--------------------------------------------------------------------------------
/samples/read-batch.py:
--------------------------------------------------------------------------------
1 | """
2 | This example reads in a tab-delimited list of DeviceAddressObjectPropertyReference
3 | and reads them as a batch.
4 |
5 | The text file contains:
6 | key - any simple value to identify the row
7 | device address - an Address, like '192.168.0.12' or '5101:16'
8 | object identifier - an ObjectIdentifier, like 'analog-value,12'
9 | property reference - a PropertyReference, like 'present-value'
10 |
11 | The property reference can also include an array index like 'priority-array[6]'.
12 | """
13 |
14 | import sys
15 | import asyncio
16 |
17 | from typing import Union
18 |
19 | from bacpypes3.settings import settings
20 | from bacpypes3.debugging import ModuleLogger
21 | from bacpypes3.argparse import SimpleArgumentParser
22 | from bacpypes3.app import Application
23 |
24 | from bacpypes3.apdu import ErrorRejectAbortNack
25 | from bacpypes3.lib.batchread import DeviceAddressObjectPropertyReference, BatchRead
26 |
27 | # some debugging
28 | _debug = 0
29 | _log = ModuleLogger(globals())
30 |
31 | # globals
32 | app: Application
33 | batch_read: BatchRead
34 |
35 |
36 | def callback(key: str, value: Union[float, ErrorRejectAbortNack]) -> None:
37 | """
38 | This is called when the batch has found a value or an error. The alternative
39 | to using a callback is to wait for the batch to complete and zip() the
40 | results with the list of keys.
41 | """
42 | if isinstance(value, ErrorRejectAbortNack):
43 | print(f"{key}: {value.errorClass}, {value.errorCode}")
44 | else:
45 | print(f"{key} = {value}")
46 |
47 |
48 | async def main() -> None:
49 | global app, batch_read
50 |
51 | try:
52 | app = None
53 | args = SimpleArgumentParser().parse_args()
54 | if _debug:
55 | _log.debug("settings: %r", settings)
56 |
57 | # build the application
58 | app = Application.from_args(args)
59 |
60 | # transform the list of stuff to read
61 | daopr_list = []
62 | while line := sys.stdin.readline():
63 | line_args = line[:-1].split("\t")
64 | daopr_list.append(DeviceAddressObjectPropertyReference(*line_args))
65 | batch_read = BatchRead(daopr_list)
66 |
67 | # run until the batch is done
68 | await batch_read.run(app, callback)
69 |
70 | except KeyboardInterrupt:
71 | if _debug:
72 | _log.debug("keyboard interrupt")
73 | finally:
74 | if app:
75 | app.close()
76 |
77 |
78 | if __name__ == "__main__":
79 | asyncio.run(main())
80 |
--------------------------------------------------------------------------------
/samples/read-property-console.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends Read Property requests.
3 | """
4 |
5 | import asyncio
6 | import re
7 |
8 | from typing import Callable
9 |
10 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
11 | from bacpypes3.argparse import SimpleArgumentParser
12 | from bacpypes3.console import Console
13 | from bacpypes3.cmd import Cmd
14 | from bacpypes3.comm import bind
15 |
16 | from bacpypes3.pdu import Address
17 | from bacpypes3.primitivedata import ObjectIdentifier
18 | from bacpypes3.constructeddata import AnyAtomic
19 | from bacpypes3.apdu import ErrorRejectAbortNack
20 | from bacpypes3.app import Application
21 |
22 | # some debugging
23 | _debug = 0
24 | _log = ModuleLogger(globals())
25 |
26 | # 'property[index]' matching
27 | property_index_re = re.compile(r"^([0-9A-Za-z-]+)(?:\[([0-9]+)\])?$")
28 |
29 | # globals
30 | app: Application
31 |
32 |
33 | @bacpypes_debugging
34 | class SampleCmd(Cmd):
35 | """
36 | Sample Cmd
37 | """
38 |
39 | _debug: Callable[..., None]
40 |
41 | async def do_read(
42 | self,
43 | address: Address,
44 | object_identifier: ObjectIdentifier,
45 | property_identifier: str,
46 | ) -> None:
47 | """
48 | usage: read address objid prop[indx]
49 | """
50 | if _debug:
51 | SampleCmd._debug(
52 | "do_read %r %r %r", address, object_identifier, property_identifier
53 | )
54 |
55 | try:
56 | property_value = await app.read_property(
57 | address, object_identifier, property_identifier
58 | )
59 | if _debug:
60 | SampleCmd._debug(" - property_value: %r", property_value)
61 | except ErrorRejectAbortNack as err:
62 | if _debug:
63 | SampleCmd._debug(" - exception: %r", err)
64 | property_value = err
65 |
66 | if isinstance(property_value, AnyAtomic):
67 | if _debug:
68 | SampleCmd._debug(" - schedule objects")
69 | property_value = property_value.get_value()
70 |
71 | await self.response(str(property_value))
72 |
73 |
74 | async def main() -> None:
75 | global app
76 |
77 | app = None
78 | try:
79 | parser = SimpleArgumentParser()
80 | args = parser.parse_args()
81 | if _debug:
82 | _log.debug("args: %r", args)
83 |
84 | # build a very small stack
85 | console = Console()
86 | cmd = SampleCmd()
87 | bind(console, cmd)
88 |
89 | # build an application
90 | app = Application.from_args(args)
91 | if _debug:
92 | _log.debug("app: %r", app)
93 |
94 | # wait until the user is done
95 | await console.fini.wait()
96 |
97 | except KeyboardInterrupt:
98 | if _debug:
99 | _log.debug("keyboard interrupt")
100 | finally:
101 | if app:
102 | app.close()
103 |
104 |
105 | if __name__ == "__main__":
106 | asyncio.run(main())
107 |
--------------------------------------------------------------------------------
/samples/read-property.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends a Read Property request and decodes the response.
3 | """
4 |
5 | import asyncio
6 | import re
7 |
8 | from bacpypes3.debugging import ModuleLogger
9 | from bacpypes3.argparse import SimpleArgumentParser
10 | from bacpypes3.app import Application
11 |
12 | from bacpypes3.constructeddata import AnyAtomic
13 | from bacpypes3.apdu import ErrorRejectAbortNack
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 | # 'property[index]' matching
20 | property_index_re = re.compile(r"^([0-9A-Za-z-]+)(?:\[([0-9]+)\])?$")
21 |
22 |
23 | async def main() -> None:
24 | app = None
25 | try:
26 | parser = SimpleArgumentParser()
27 | parser.add_argument(
28 | "device_address",
29 | help="address of the server (B-device)",
30 | )
31 | parser.add_argument(
32 | "object_identifier",
33 | help="object identifier, like 'analog-input,1'",
34 | )
35 | parser.add_argument(
36 | "property_identifier",
37 | help="property identifier with optional array index, like 'present-value'",
38 | )
39 | args = parser.parse_args()
40 | if _debug:
41 | _log.debug("args: %r", args)
42 |
43 | # build an application
44 | app = Application.from_args(args)
45 | if _debug:
46 | _log.debug("app: %r", app)
47 |
48 | try:
49 | response = await app.read_property(
50 | args.device_address,
51 | args.object_identifier,
52 | args.property_identifier,
53 | )
54 | if _debug:
55 | _log.debug(" - response: %r", response)
56 | except ErrorRejectAbortNack as err:
57 | if _debug:
58 | _log.debug(" - exception: %r", err)
59 | response = err
60 |
61 | if isinstance(response, AnyAtomic):
62 | if _debug:
63 | _log.debug(" - schedule objects")
64 | response = response.get_value()
65 |
66 | print(str(response))
67 |
68 | finally:
69 | if app:
70 | app.close()
71 |
72 |
73 | if __name__ == "__main__":
74 | asyncio.run(main())
75 |
--------------------------------------------------------------------------------
/samples/start-here.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example.
3 | """
4 |
5 | import asyncio
6 |
7 | from bacpypes3.debugging import ModuleLogger
8 | from bacpypes3.argparse import SimpleArgumentParser
9 | from bacpypes3.app import Application
10 |
11 | # some debugging
12 | _debug = 0
13 | _log = ModuleLogger(globals())
14 |
15 |
16 | async def main() -> None:
17 | app = None
18 | try:
19 | args = SimpleArgumentParser().parse_args()
20 | if _debug:
21 | _log.debug("args: %r", args)
22 |
23 | # build an application
24 | app = Application.from_args(args)
25 | if _debug:
26 | _log.debug("app: %r", app)
27 |
28 | # like running forever
29 | await asyncio.Future()
30 |
31 | finally:
32 | if app:
33 | app.close()
34 |
35 |
36 | if __name__ == "__main__":
37 | try:
38 | asyncio.run(main())
39 | except KeyboardInterrupt:
40 | if _debug:
41 | _log.debug("keyboard interrupt")
42 |
--------------------------------------------------------------------------------
/samples/who-has.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends a Who-Has request for an object identifier and prints
3 | out the device instance number, object identifier and object name of the
4 | responses.
5 | """
6 |
7 | import asyncio
8 |
9 | from bacpypes3.debugging import ModuleLogger
10 | from bacpypes3.argparse import SimpleArgumentParser
11 | from bacpypes3.primitivedata import ObjectIdentifier
12 | from bacpypes3.app import Application
13 |
14 | # some debugging
15 | _debug = 0
16 | _log = ModuleLogger(globals())
17 |
18 |
19 | async def main() -> None:
20 | app = None
21 | try:
22 | parser = SimpleArgumentParser()
23 | parser.add_argument(
24 | "low_limit",
25 | type=int,
26 | help="device instance range low limit",
27 | )
28 | parser.add_argument(
29 | "high_limit",
30 | type=int,
31 | help="device instance range high limit",
32 | )
33 | parser.add_argument(
34 | "object_identifier",
35 | help="object identifier",
36 | )
37 | args = parser.parse_args()
38 | if _debug:
39 | _log.debug("args: %r", args)
40 |
41 | # build an application
42 | app = Application.from_args(args)
43 | if _debug:
44 | _log.debug("app: %r", app)
45 |
46 | object_identifier = ObjectIdentifier(args.object_identifier)
47 | if _debug:
48 | _log.debug("object_identifier: %r", object_identifier)
49 |
50 | # run the query
51 | i_haves = await app.who_has(
52 | args.low_limit,
53 | args.high_limit,
54 | object_identifier,
55 | )
56 | if _debug:
57 | _log.debug(" - i_haves: %r", i_haves)
58 | for i_have in i_haves:
59 | if _debug:
60 | _log.debug(" - i_have: %r", i_have)
61 | print(
62 | f"{i_have.deviceIdentifier[1]} {i_have.objectIdentifier} {i_have.objectName!r}"
63 | )
64 |
65 | finally:
66 | if app:
67 | app.close()
68 |
69 |
70 | if __name__ == "__main__":
71 | asyncio.run(main())
72 |
--------------------------------------------------------------------------------
/samples/write-property.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that sends a Write Property request.
3 | """
4 |
5 | import asyncio
6 | import re
7 |
8 | from bacpypes3.debugging import ModuleLogger
9 | from bacpypes3.argparse import SimpleArgumentParser
10 | from bacpypes3.app import Application
11 |
12 | from bacpypes3.pdu import Address
13 | from bacpypes3.primitivedata import ObjectIdentifier
14 | from bacpypes3.apdu import ErrorRejectAbortNack
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 | # 'property[index]' matching
21 | property_index_re = re.compile(r"^([0-9A-Za-z-]+)(?:\[([0-9]+)\])?$")
22 |
23 |
24 | async def main() -> None:
25 | app = None
26 | try:
27 | parser = SimpleArgumentParser()
28 | parser.add_argument(
29 | "device_address",
30 | help="address of the server (B-device)",
31 | )
32 | parser.add_argument(
33 | "object_identifier",
34 | help="object identifier",
35 | )
36 | parser.add_argument(
37 | "property_identifier",
38 | help="property identifier with optional array index",
39 | )
40 | parser.add_argument(
41 | "value",
42 | help="value to write",
43 | )
44 | parser.add_argument(
45 | "priority",
46 | nargs="?",
47 | help="optional priority",
48 | )
49 | args = parser.parse_args()
50 | if _debug:
51 | _log.debug("args: %r", args)
52 |
53 | # interpret the address
54 | device_address = Address(args.device_address)
55 | if _debug:
56 | _log.debug("device_address: %r", device_address)
57 |
58 | # interpret the object identifier
59 | object_identifier = ObjectIdentifier(args.object_identifier)
60 | if _debug:
61 | _log.debug("object_identifier: %r", object_identifier)
62 |
63 | # split the property identifier and its index
64 | property_index_match = property_index_re.match(args.property_identifier)
65 | if not property_index_match:
66 | raise ValueError("property specification incorrect")
67 | property_identifier, property_array_index = property_index_match.groups()
68 | if property_identifier.isdigit():
69 | property_identifier = int(property_identifier)
70 | if property_array_index is not None:
71 | property_array_index = int(property_array_index)
72 |
73 | # check the priority
74 | priority = None
75 | if args.priority:
76 | priority = int(args.priority)
77 | if (priority < 1) or (priority > 16):
78 | raise ValueError(f"priority: {priority}")
79 | if _debug:
80 | _log.debug("priority: %r", priority)
81 |
82 | # build an application
83 | app = Application.from_args(args)
84 | if _debug:
85 | _log.debug("app: %r", app)
86 |
87 | try:
88 | response = await app.write_property(
89 | device_address,
90 | object_identifier,
91 | property_identifier,
92 | args.value,
93 | property_array_index,
94 | priority,
95 | )
96 | if _debug:
97 | _log.debug("response: %r", response)
98 | except ErrorRejectAbortNack as err:
99 | print(str(err))
100 |
101 | finally:
102 | if app:
103 | app.close()
104 |
105 |
106 | if __name__ == "__main__":
107 | asyncio.run(main())
108 |
--------------------------------------------------------------------------------
/sandbox/discover-ipv4-foreign-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker build \
4 | --tag bacpypes3-discover-ipv4-foreign:latest \
5 | --file discover-ipv4-foreign.dockerfile .
6 |
--------------------------------------------------------------------------------
/sandbox/discover-ipv4-foreign-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ -z "${HOST_ADDRESS}" ]]
4 | then
5 | echo "The HOST_ADDRESS is the UDP port number of the docker host"
6 | echo "like '47808' or an IPv4 address and port number if the host"
7 | echo "has more than one home, like '10.0.1.70:47808' and may also"
8 | echo "have an alternative port number like '10.0.1.99:47809'"
9 | echo ""
10 | read -p "Host Address " HOST_ADDRESS || exit 1
11 | export HOST_ADDRESS
12 | echo ""
13 | fi
14 | if [[ -z "${BBMD_ADDRESS}" ]]
15 | then
16 | echo "The address of the BBMD for foreign device registration"
17 | echo ""
18 | read -p "BBMD Address " BBMD_ADDRESS || exit 1
19 | export BBMD_ADDRESS
20 | fi
21 | if [[ -z "${TTL}" ]]
22 | then
23 | read -p "Time-to-live " TTL || exit 1
24 | export TTL
25 | fi
26 | if [[ -z "${DEBUG}" ]]
27 | then
28 | export DEBUG=""
29 | fi
30 |
31 | docker run \
32 | -it \
33 | --rm \
34 | -p ${HOST_ADDRESS}:47808/udp \
35 | --env BBMD_ADDRESS \
36 | --env TTL \
37 | --env DEBUG \
38 | bacpypes3-discover-ipv4-foreign:latest
39 |
--------------------------------------------------------------------------------
/sandbox/discover-ipv4-foreign-save.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker save bacpypes3-discover-ipv4-foreign:latest > discover-ipv4-foreign.tar
4 |
--------------------------------------------------------------------------------
/sandbox/discover-ipv4-foreign.dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-slim
2 |
3 | WORKDIR /app
4 |
5 | RUN pip install --upgrade pip
6 | RUN pip install --upgrade --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ bacpypes3
7 |
8 | COPY discover-ipv4-foreign.py .
9 |
10 | ARG BBMD_ADDRESS
11 | ARG TTL
12 | ARG DEBUG=
13 |
14 | CMD python3 discover-ipv4-foreign.py host:47808 ${BBMD_ADDRESS} ${TTL} --route-aware ${DEBUG}
15 |
--------------------------------------------------------------------------------
/sandbox/event-notification-recipient.py:
--------------------------------------------------------------------------------
1 | """
2 | Event Notification Recipient
3 | """
4 |
5 | import asyncio
6 |
7 | from typing import Callable
8 |
9 | from bacpypes3.debugging import ModuleLogger, bacpypes_debugging
10 | from bacpypes3.argparse import SimpleArgumentParser
11 | from bacpypes3.apdu import SimpleAckPDU
12 | from bacpypes3.app import Application
13 |
14 | # some debugging
15 | _debug = 0
16 | _log = ModuleLogger(globals())
17 |
18 |
19 | @bacpypes_debugging
20 | class EventRecipientApplication(Application):
21 | """
22 | Event Recipient Application
23 | """
24 |
25 | _debug: Callable[..., None]
26 |
27 | async def do_ConfirmedEventNotificationRequest(self, apdu):
28 | print("ConfirmedEventNotificationRequest")
29 | apdu.debug_contents()
30 |
31 | await self.response(SimpleAckPDU(context=apdu))
32 |
33 | async def do_UnconfirmedEventNotificationRequest(self, apdu):
34 | print("UnconfirmedEventNotificationRequest")
35 | apdu.debug_contents()
36 |
37 |
38 | async def main() -> None:
39 | app = None
40 | try:
41 | args = SimpleArgumentParser().parse_args()
42 | if _debug:
43 | _log.debug("args: %r", args)
44 |
45 | # build an application
46 | app = EventRecipientApplication.from_args(args)
47 | if _debug:
48 | _log.debug("app: %r", app)
49 |
50 | # like running forever
51 | await asyncio.Future()
52 |
53 | finally:
54 | if app:
55 | app.close()
56 |
57 |
58 | if __name__ == "__main__":
59 | try:
60 | asyncio.run(main())
61 | except KeyboardInterrupt:
62 | if _debug:
63 | _log.debug("keyboard interrupt")
64 |
--------------------------------------------------------------------------------
/sandbox/get-ip-address.py:
--------------------------------------------------------------------------------
1 | """
2 | Get the IPv4 address of an interface.
3 |
4 | https://gist.github.com/socketz/fc9bbbba7be561852ae8905e277402b8
5 | """
6 | import sys
7 | import socket
8 | import fcntl
9 | import struct
10 |
11 | SIOCGIFADDR = 0x8915
12 |
13 |
14 | def get_ip_address(ifname):
15 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
16 | fdsock = s.fileno()
17 |
18 | ifreq = struct.pack("16sH14s", ifname.encode(), socket.AF_INET, b"\x00" * 14)
19 | try:
20 | res = fcntl.ioctl(fdsock, SIOCGIFADDR, ifreq)
21 | except:
22 | return None
23 | return socket.inet_ntoa(struct.unpack("16sH2x4s8x", res)[2])
24 |
25 |
26 | print(get_ip_address(sys.argv[1]))
27 |
--------------------------------------------------------------------------------
/sandbox/get-netmask.py:
--------------------------------------------------------------------------------
1 | """
2 | Get the network mask of the interface.
3 | """
4 | import sys
5 | import socket
6 | import fcntl
7 | import struct
8 |
9 | SIOCGIFNETMASK = 0x891B
10 |
11 |
12 | def get_netmask(ifname):
13 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
14 | return socket.inet_ntoa(
15 | fcntl.ioctl(s.fileno(), SIOCGIFNETMASK, struct.pack("256s", ifname.encode()))[
16 | 20:24
17 | ]
18 | )
19 |
20 |
21 | print(get_netmask(sys.argv[1]))
22 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv4-bbmd-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple IPv4 BBMD server that does _not_ have a network layer or application
3 | layer.
4 | """
5 |
6 | from __future__ import annotations
7 |
8 | import asyncio
9 |
10 | from typing import Callable
11 |
12 | from bacpypes3.settings import settings
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 |
16 | from bacpypes3.pdu import IPv4Address, PDU
17 | from bacpypes3.comm import Client, bind
18 |
19 | from bacpypes3.ipv4.service import UDPMultiplexer, BIPBBMD
20 | from bacpypes3.ipv4.bvll import BVLLCodec
21 | from bacpypes3.ipv4 import IPv4DatagramServer
22 |
23 |
24 | # some debugging
25 | _debug = 0
26 | _log = ModuleLogger(globals())
27 |
28 |
29 | def main() -> None:
30 | try:
31 | loop = server = None
32 | parser = ArgumentParser()
33 | parser.add_argument(
34 | "address",
35 | type=str,
36 | help="listening address",
37 | )
38 | parser.add_argument(
39 | "peers",
40 | type=str,
41 | nargs="*",
42 | help="peer addresses",
43 | )
44 |
45 | args = parser.parse_args()
46 | if _debug:
47 | _log.debug("settings: %r", settings)
48 |
49 | # get the event loop
50 | loop = asyncio.get_event_loop()
51 |
52 | # evaluate the address
53 | address = IPv4Address(args.address)
54 | if _debug:
55 | _log.debug("address: %r", address)
56 |
57 | # build a very small stack
58 | bbmd = BIPBBMD(address)
59 | for peer in args.peers:
60 | peer_address = IPv4Address(peer)
61 | bbmd.add_peer(peer_address)
62 |
63 | codec = BVLLCodec()
64 | multiplexer = UDPMultiplexer()
65 | server = IPv4DatagramServer(loop, address)
66 |
67 | bind(bbmd, codec, multiplexer.annexJ)
68 | bind(multiplexer, server)
69 |
70 | # run a really long time
71 | loop.run_forever()
72 |
73 | except KeyboardInterrupt:
74 | if _debug:
75 | _log.debug("keyboard interrupt")
76 | finally:
77 | if server:
78 | server.close()
79 | if loop:
80 | loop.close()
81 |
82 |
83 | if __name__ == "__main__":
84 | main()
85 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv4-normal-client.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that sends messages.
3 | """
4 |
5 | import sys
6 | import asyncio
7 |
8 | from typing import Callable
9 |
10 | from bacpypes3.settings import settings
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.argparse import ArgumentParser
13 | from bacpypes3.console import Console
14 | from bacpypes3.cmd import Cmd
15 |
16 | from bacpypes3.pdu import Address, LocalBroadcast, IPv4Address, PDU
17 | from bacpypes3.comm import Client, bind
18 |
19 | from bacpypes3.ipv4.service import UDPMultiplexer, BIPNormal
20 | from bacpypes3.ipv4.bvll import BVLLCodec
21 | from bacpypes3.ipv4 import IPv4DatagramServer
22 |
23 |
24 | # some debugging
25 | _debug = 0
26 | _log = ModuleLogger(globals())
27 |
28 |
29 | @bacpypes_debugging
30 | class SampleCmd(Cmd, Client[PDU]):
31 | """
32 | Sample Cmd
33 | """
34 |
35 | _debug: Callable[..., None]
36 |
37 | async def do_send(self, address: Address, data: str) -> None:
38 | """
39 | usage: send address:Address data:str
40 | """
41 | if _debug:
42 | SampleCmd._debug("do_send %r %r", address, data)
43 |
44 | if not isinstance(address, (IPv4Address, LocalBroadcast)):
45 | if _debug:
46 | SampleCmd._debug(" - invalid address: %r", address)
47 | await self.response("invalid address: " + str(address))
48 | return
49 |
50 | pdu = PDU(data.encode(), destination=address)
51 | if _debug:
52 | SampleCmd._debug(" - pdu: %r", pdu)
53 |
54 | await self.request(pdu)
55 |
56 | async def confirmation(self, pdu: PDU) -> None:
57 | if _debug:
58 | SampleCmd._debug("confirmation %r", pdu)
59 |
60 | await self.response(pdu.pduData.decode())
61 |
62 |
63 | def main() -> None:
64 | try:
65 | loop = console = cmd = server = None
66 | parser = ArgumentParser()
67 | parser.add_argument(
68 | "address",
69 | type=str,
70 | help="address",
71 | )
72 |
73 | args = parser.parse_args()
74 | if _debug:
75 | _log.debug("settings: %r", settings)
76 |
77 | # get the event loop
78 | loop = asyncio.get_event_loop()
79 |
80 | # evaluate the address
81 | address = IPv4Address(args.address)
82 | if _debug:
83 | _log.debug("address: %r", address)
84 |
85 | # build a very small stack
86 | console = Console()
87 | cmd = SampleCmd()
88 | simple = BIPNormal()
89 | codec = BVLLCodec()
90 | multiplexer = UDPMultiplexer()
91 | server = IPv4DatagramServer(loop, address)
92 |
93 | bind(console, cmd, simple, codec, multiplexer.annexJ) # type: ignore[arg-type]
94 | bind(multiplexer, server) # type: ignore[arg-type]
95 |
96 | # run until the console is done, canceled or EOF
97 | loop.run_until_complete(console.fini.wait())
98 |
99 | except KeyboardInterrupt:
100 | if _debug:
101 | _log.debug("keyboard interrupt")
102 | finally:
103 | if server:
104 | server.close()
105 | if loop:
106 | loop.close()
107 | if console and console.exit_status:
108 | sys.exit(console.exit_status)
109 |
110 |
111 | if __name__ == "__main__":
112 | main()
113 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv4-normal-server-broadcast.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that echos PDU messages, upper cased, and broadcasts
3 | the response.
4 | """
5 |
6 | import asyncio
7 |
8 | from typing import Callable
9 |
10 | from bacpypes3.settings import settings
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.argparse import ArgumentParser
13 |
14 | from bacpypes3.pdu import LocalBroadcast, IPv4Address, PDU
15 | from bacpypes3.comm import Client, bind
16 |
17 | from bacpypes3.ipv4.service import UDPMultiplexer, BIPNormal
18 | from bacpypes3.ipv4.bvll import BVLLCodec
19 | from bacpypes3.ipv4 import IPv4DatagramServer
20 |
21 |
22 | # some debugging
23 | _debug = 0
24 | _log = ModuleLogger(globals())
25 |
26 |
27 | @bacpypes_debugging
28 | class Echo(Client[PDU]):
29 | """
30 | Echo
31 | """
32 |
33 | _debug: Callable[..., None]
34 |
35 | async def confirmation(self, pdu: PDU) -> None:
36 | if _debug:
37 | Echo._debug("confirmation %r", pdu)
38 |
39 | ack = PDU(pdu.pduData.upper(), destination=LocalBroadcast())
40 | if _debug:
41 | Echo._debug(" - ack: %r", ack)
42 |
43 | await self.request(ack)
44 |
45 |
46 | def main() -> None:
47 | try:
48 | loop = echo = server = None
49 | parser = ArgumentParser()
50 | parser.add_argument(
51 | "address",
52 | type=str,
53 | help="listening address",
54 | )
55 |
56 | args = parser.parse_args()
57 | if _debug:
58 | _log.debug("settings: %r", settings)
59 |
60 | # get the event loop
61 | loop = asyncio.get_event_loop()
62 |
63 | # evaluate the address
64 | address = IPv4Address(args.address)
65 | if _debug:
66 | _log.debug("address: %r", address)
67 |
68 | # build a very small stack
69 | echo = Echo()
70 | simple = BIPNormal()
71 | codec = BVLLCodec()
72 | multiplexer = UDPMultiplexer()
73 | server = IPv4DatagramServer(loop, address)
74 |
75 | bind(echo, simple, codec, multiplexer.annexJ)
76 | bind(multiplexer, server)
77 |
78 | # run a really long time
79 | loop.run_forever()
80 |
81 | except KeyboardInterrupt:
82 | if _debug:
83 | _log.debug("keyboard interrupt")
84 | finally:
85 | if server:
86 | server.close()
87 | if loop:
88 | loop.close()
89 |
90 |
91 | if __name__ == "__main__":
92 | main()
93 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv4-normal-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that echos PDU messages, upper cased.
3 | """
4 |
5 | import asyncio
6 |
7 | from typing import Callable
8 |
9 | from bacpypes3.settings import settings
10 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
11 | from bacpypes3.argparse import ArgumentParser
12 |
13 | from bacpypes3.pdu import IPv4Address, PDU
14 | from bacpypes3.comm import Client, bind
15 |
16 | from bacpypes3.ipv4.service import UDPMultiplexer, BIPNormal
17 | from bacpypes3.ipv4.bvll import BVLLCodec
18 | from bacpypes3.ipv4 import IPv4DatagramServer
19 |
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class Echo(Client[PDU]):
28 | """
29 | Echo
30 | """
31 |
32 | _debug: Callable[..., None]
33 |
34 | async def confirmation(self, pdu: PDU) -> None:
35 | if _debug:
36 | Echo._debug("confirmation %r", pdu)
37 |
38 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
39 | if _debug:
40 | Echo._debug(" - ack: %r", ack)
41 |
42 | await self.request(ack)
43 |
44 |
45 | def main() -> None:
46 | try:
47 | loop = echo = server = None
48 | parser = ArgumentParser()
49 | parser.add_argument(
50 | "address",
51 | type=str,
52 | help="listening address",
53 | )
54 |
55 | args = parser.parse_args()
56 | if _debug:
57 | _log.debug("settings: %r", settings)
58 |
59 | # get the event loop
60 | loop = asyncio.get_event_loop()
61 |
62 | # evaluate the address
63 | address = IPv4Address(args.address)
64 | if _debug:
65 | _log.debug("address: %r", address)
66 |
67 | # build a very small stack
68 | echo = Echo()
69 | simple = BIPNormal()
70 | codec = BVLLCodec()
71 | multiplexer = UDPMultiplexer()
72 | server = IPv4DatagramServer(loop, address)
73 |
74 | bind(echo, simple, codec, multiplexer.annexJ)
75 | bind(multiplexer, server)
76 |
77 | # run a really long time
78 | loop.run_forever()
79 |
80 | except KeyboardInterrupt:
81 | if _debug:
82 | _log.debug("keyboard interrupt")
83 | finally:
84 | if server:
85 | server.close()
86 | if loop:
87 | loop.close()
88 |
89 |
90 | if __name__ == "__main__":
91 | main()
92 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv6-bbmd-server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Simple IPv6 BBMD server that does _not_ have a network layer or application
5 | layer.
6 | """
7 |
8 | import asyncio
9 |
10 | from typing import Callable
11 |
12 | from bacpypes3.settings import settings
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 |
16 | from bacpypes3.pdu import IPv6Address, VirtualAddress, PDU
17 | from bacpypes3.comm import Client, bind
18 |
19 | from bacpypes3.ipv6.service import BIPBBMD, BVLLServiceElement
20 | from bacpypes3.ipv6.bvll import BVLLCodec
21 | from bacpypes3.ipv6 import IPv6DatagramServer
22 |
23 |
24 | # some debugging
25 | _debug = 0
26 | _log = ModuleLogger(globals())
27 |
28 |
29 | def main() -> None:
30 | try:
31 | loop = server = None
32 | parser = ArgumentParser()
33 | parser.add_argument(
34 | "address",
35 | type=str,
36 | help="listening address",
37 | )
38 | parser.add_argument(
39 | "virtual_address",
40 | type=str,
41 | help="virtual address",
42 | )
43 |
44 | args = parser.parse_args()
45 | if _debug:
46 | _log.debug("settings: %r", settings)
47 |
48 | # get the event loop
49 | loop = asyncio.get_event_loop()
50 |
51 | # evaluate the addresses
52 | address = IPv6Address(args.address)
53 | if _debug:
54 | _log.debug("address: %r", address)
55 | virtual_address = VirtualAddress(args.virtual_address)
56 | if _debug:
57 | _log.debug("virtual_address: %r", virtual_address)
58 |
59 | # build a very small stack
60 | bbmd = BIPBBMD(bbmd_address=address, virtual_address=virtual_address)
61 | codec = BVLLCodec()
62 | server = IPv6DatagramServer(loop, address)
63 |
64 | bind(bbmd, codec, server)
65 |
66 | bvll_service_element = BVLLServiceElement()
67 | bind(bvll_service_element, normal)
68 |
69 | # run a really long time
70 | loop.run_forever()
71 |
72 | except KeyboardInterrupt:
73 | if _debug:
74 | _log.debug("keyboard interrupt")
75 | finally:
76 | if server:
77 | server.close()
78 | if loop:
79 | loop.close()
80 |
81 |
82 | if __name__ == "__main__":
83 | main()
84 |
--------------------------------------------------------------------------------
/sandbox/link-layer/ipv6-normal-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that echos PDU messages, upper cased.
3 | """
4 |
5 | import asyncio
6 |
7 | from typing import Callable
8 |
9 | from bacpypes3.settings import settings
10 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
11 | from bacpypes3.argparse import ArgumentParser
12 |
13 | from bacpypes3.pdu import IPv6Address, VirtualAddress, PDU
14 | from bacpypes3.comm import Client, bind
15 |
16 | from bacpypes3.ipv6.service import BIPNormal, BVLLServiceElement
17 | from bacpypes3.ipv6.bvll import BVLLCodec
18 | from bacpypes3.ipv6 import IPv6DatagramServer
19 |
20 |
21 | # some debugging
22 | _debug = 0
23 | _log = ModuleLogger(globals())
24 |
25 |
26 | @bacpypes_debugging
27 | class Echo(Client[PDU]):
28 | """
29 | Echo
30 | """
31 |
32 | _debug: Callable[..., None]
33 |
34 | async def confirmation(self, pdu: PDU) -> None:
35 | if _debug:
36 | Echo._debug("confirmation %r", pdu)
37 |
38 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
39 | if _debug:
40 | Echo._debug(" - ack: %r", ack)
41 |
42 | await self.request(ack)
43 |
44 |
45 | def main() -> None:
46 | try:
47 | loop = echo = server = None
48 | parser = ArgumentParser()
49 | parser.add_argument(
50 | "address",
51 | type=str,
52 | help="listening address",
53 | )
54 | parser.add_argument(
55 | "virtual_address",
56 | type=str,
57 | help="virtual address",
58 | )
59 |
60 | args = parser.parse_args()
61 | if _debug:
62 | _log.debug("settings: %r", settings)
63 |
64 | # get the event loop
65 | loop = asyncio.get_event_loop()
66 |
67 | # evaluate the addresses
68 | address = IPv6Address(args.address)
69 | if _debug:
70 | _log.debug("address: %r", address)
71 | virtual_address = VirtualAddress(args.virtual_address)
72 | if _debug:
73 | _log.debug("virtual_address: %r", virtual_address)
74 |
75 | # build a very small stack
76 | echo = Echo()
77 | normal = BIPNormal(virtual_address=virtual_address)
78 | codec = BVLLCodec()
79 | server = IPv6DatagramServer(loop, address)
80 |
81 | bind(echo, normal, codec, server)
82 |
83 | bvll_service_element = BVLLServiceElement()
84 | bind(bvll_service_element, normal)
85 |
86 | # run a really long time
87 | loop.run_forever()
88 |
89 | except KeyboardInterrupt:
90 | if _debug:
91 | _log.debug("keyboard interrupt")
92 | finally:
93 | if server:
94 | server.close()
95 | if loop:
96 | loop.close()
97 |
98 |
99 | if __name__ == "__main__":
100 | main()
101 |
--------------------------------------------------------------------------------
/sandbox/mqtt-b-side.py:
--------------------------------------------------------------------------------
1 | """
2 | MQTT B-side Example
3 |
4 | When the present-value of an Analog Object is changed, publish a message to the
5 | broker using the object name as the topic.
6 | """
7 |
8 | import asyncio
9 |
10 | from typing import Dict, Tuple
11 |
12 | from bacpypes3.debugging import ModuleLogger
13 | from bacpypes3.argparse import SimpleArgumentParser
14 | from bacpypes3.primitivedata import ObjectIdentifier, Real
15 | from bacpypes3.app import Application
16 | from bacpypes3.local.analog import AnalogValueObject
17 |
18 | import aiomqtt
19 |
20 | # some debugging
21 | _debug = 0
22 | _log = ModuleLogger(globals())
23 |
24 | MQTT_HOST = "test.mosquitto.org"
25 | MQTT_TOPIC_PREFIX = "bacpypes3/mqtt"
26 |
27 | # globals
28 | args = None
29 | client = None
30 |
31 | topic_map: Dict[str, ObjectIdentifier] = {
32 | "oat": ObjectIdentifier("analog-value,1"),
33 | "rh": ObjectIdentifier("analog-value,2"),
34 | }
35 |
36 |
37 | class MQTTAnalogValueObject(AnalogValueObject):
38 | _present_value: float = 0.0
39 |
40 | @property
41 | async def presentValue(self) -> Real:
42 | if _debug:
43 | _log.debug("presentValue (getter)")
44 |
45 | return Real(self._present_value)
46 |
47 | @presentValue.setter
48 | async def presentValue(self, value: Real) -> None:
49 | """Change the present value."""
50 | if _debug:
51 | _log.debug("presentValue (setter) %r", value)
52 | global client
53 |
54 | self._present_value = value
55 |
56 | await client.publish(args.topic + "/" + self.objectName, payload=value)
57 |
58 |
59 | async def main() -> None:
60 | global args, client
61 |
62 | app = None
63 | try:
64 | parser = SimpleArgumentParser()
65 | parser.add_argument(
66 | "--topic",
67 | help="topic prefix",
68 | default=MQTT_TOPIC_PREFIX,
69 | )
70 | parser.add_argument(
71 | "--host",
72 | help="host name of the broker",
73 | default=MQTT_HOST,
74 | )
75 | args = parser.parse_args()
76 | if _debug:
77 | _log.debug("args: %r", args)
78 |
79 | # build an application
80 | app = Application.from_args(args)
81 | if _debug:
82 | _log.debug("app: %r", app)
83 |
84 | # create objects
85 | for object_name, object_identifier in topic_map.items():
86 | # create a custom object
87 | analog_value_object = MQTTAnalogValueObject(
88 | objectName=object_name,
89 | objectIdentifier=(object_identifier),
90 | presentValue=0.0,
91 | )
92 | if _debug:
93 | _log.debug("analog_value_object: %r", analog_value_object)
94 |
95 | app.add_object(analog_value_object)
96 |
97 | # connect and run forever
98 | async with aiomqtt.Client(args.host) as client:
99 | await asyncio.Future()
100 |
101 | finally:
102 | if app:
103 | app.close()
104 |
105 |
106 | if __name__ == "__main__":
107 | try:
108 | asyncio.run(main())
109 | except KeyboardInterrupt:
110 | if _debug:
111 | _log.debug("keyboard interrupt")
112 |
--------------------------------------------------------------------------------
/sandbox/net-normal-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple console example that echos PDU messages, upper cased.
3 | """
4 |
5 | import asyncio
6 |
7 | from typing import Callable
8 |
9 | from bacpypes3.settings import settings
10 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
11 | from bacpypes3.argparse import ArgumentParser
12 |
13 | from bacpypes3.pdu import IPv4Address, PDU
14 | from bacpypes3.comm import Client, bind
15 |
16 | from bacpypes3.netservice import NetworkServiceAccessPoint, NetworkServiceElement
17 |
18 | from bacpypes3.ipv4.service import UDPMultiplexer, BIPNormal
19 | from bacpypes3.ipv4.bvll import BVLLCodec
20 | from bacpypes3.ipv4 import IPv4DatagramServer
21 |
22 |
23 | # some debugging
24 | _debug = 0
25 | _log = ModuleLogger(globals())
26 |
27 |
28 | @bacpypes_debugging
29 | class Echo(Client[PDU]):
30 | """
31 | Echo
32 | """
33 |
34 | _debug: Callable[..., None]
35 |
36 | async def confirmation(self, pdu: PDU) -> None:
37 | if _debug:
38 | Echo._debug("confirmation %r", pdu)
39 |
40 | ack = PDU(pdu.pduData.upper(), destination=pdu.pduSource)
41 | if _debug:
42 | Echo._debug(" - ack: %r", ack)
43 |
44 | await self.request(ack)
45 |
46 |
47 | def main() -> None:
48 | try:
49 | loop = echo = server = None
50 | parser = ArgumentParser()
51 | parser.add_argument(
52 | "address",
53 | type=str,
54 | help="listening address",
55 | )
56 |
57 | args = parser.parse_args()
58 | if _debug:
59 | _log.debug("settings: %r", settings)
60 |
61 | # get the event loop
62 | loop = asyncio.get_event_loop()
63 |
64 | # evaluate the address
65 | ipv4_address = IPv4Address(args.address)
66 | if _debug:
67 | _log.debug("ipv4_address: %r", ipv4_address)
68 |
69 | # application layer
70 | echo = Echo()
71 |
72 | # network layer
73 | npdu_nsap = NetworkServiceAccessPoint()
74 | npdu_nse = NetworkServiceElement()
75 | bind(npdu_nse, npdu_nsap) # type: ignore[arg-type]
76 |
77 | # bind the upper layers together
78 | bind(echo, npdu_nsap)
79 |
80 | # create a link layer
81 | bvll_normal = BIPNormal()
82 | bvll_codec = BVLLCodec()
83 | multiplexer = UDPMultiplexer()
84 | server = IPv4DatagramServer(loop, ipv4_address)
85 | bind(multiplexer, server) # type: ignore[arg-type]
86 | bind(bvll_normal, bvll_codec, multiplexer.annexJ) # type: ignore[arg-type]
87 |
88 | # connect the network layer to the link layer
89 | npdu_nsap.bind(bvll_normal)
90 |
91 | # run a really long time
92 | loop.run_forever()
93 |
94 | except KeyboardInterrupt:
95 | if _debug:
96 | _log.debug("keyboard interrupt")
97 | finally:
98 | if server:
99 | server.close()
100 | if loop:
101 | loop.close()
102 |
103 |
104 | if __name__ == "__main__":
105 | main()
106 |
--------------------------------------------------------------------------------
/sandbox/redis-server.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple example that has a device object and an additional analog value object
3 | that matches a key in Redis.
4 | """
5 |
6 | import asyncio
7 | import os
8 |
9 | from bacpypes3.debugging import ModuleLogger
10 | from bacpypes3.argparse import SimpleArgumentParser
11 |
12 | from bacpypes3.primitivedata import Real
13 | from bacpypes3.ipv4.app import Application
14 | from bacpypes3.local.analog import AnalogValueObject
15 |
16 | from redis import asyncio as aioredis
17 | from redis.asyncio import Redis
18 |
19 | # some debugging
20 | _debug = 0
21 | _log = ModuleLogger(globals())
22 |
23 | # globals
24 | redis: Redis
25 |
26 | # settings
27 | REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
28 | REDIS_PORT = int(os.getenv("REDIS_PORT", 6379))
29 | REDIS_DB = int(os.getenv("REDIS_DB", 0))
30 |
31 |
32 | class RedisAnalogValueObject(AnalogValueObject):
33 | @property
34 | async def presentValue(self) -> Real:
35 | if _debug:
36 | _log.debug("presentValue (getter)")
37 |
38 | value = await redis.get(str(self.objectName))
39 | if _debug:
40 | _log.debug(f"{value = !r}")
41 | if value is None:
42 | return None
43 |
44 | return Real(float(value))
45 |
46 | @presentValue.setter
47 | async def presentValue(self, value: Real) -> None:
48 | """Change the present value."""
49 | if _debug:
50 | _log.debug("presentValue (setter) %r", value)
51 |
52 | await redis.set(str(self.objectName), value)
53 |
54 |
55 | async def main() -> None:
56 | global redis
57 |
58 | try:
59 | app = None
60 | args = SimpleArgumentParser().parse_args()
61 | if _debug:
62 | _log.debug("args: %r", args)
63 |
64 | # connect to Redis
65 | redis = aioredis.from_url(f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}")
66 | await redis.ping()
67 |
68 | # build an application
69 | app = Application.from_args(args)
70 | if _debug:
71 | _log.debug("app: %r", app)
72 |
73 | # create a custom object
74 | custom_object = RedisAnalogValueObject(
75 | objectIdentifier=("analog-value", 1),
76 | objectName="Wowzers",
77 | )
78 | if _debug:
79 | _log.debug("custom_object: %r", custom_object)
80 |
81 | app.add_object(custom_object)
82 |
83 | # like running forever
84 | await asyncio.Future()
85 |
86 | finally:
87 | if app:
88 | app.close()
89 |
90 |
91 | if __name__ == "__main__":
92 | asyncio.run(main())
93 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import re
6 |
7 | from setuptools import setup, find_packages
8 |
9 | # load in the project metadata
10 | init_py = open(os.path.join("bacpypes3", "__init__.py")).read()
11 | metadata = dict(re.findall("""__([a-z]+)__ = ["]([^"]+)["]""", init_py))
12 |
13 | requirements = []
14 |
15 | setup(
16 | name="bacpypes3",
17 | version=metadata["version"],
18 | description="BACnet Communications Library",
19 | long_description="BACpypes3 provides a BACnet application layer and network layer written in Python3 for daemons, scripting, and graphical interfaces.",
20 | long_description_content_type="text/x-rst",
21 | author=metadata["author"],
22 | author_email=metadata["email"],
23 | url="https://github.com/JoelBender/bacpypes3",
24 | packages=find_packages(),
25 | package_data={"bacpypes3": ["py.typed"]},
26 | include_package_data=True,
27 | install_requires=requirements,
28 | license="MIT",
29 | zip_safe=False,
30 | classifiers=[
31 | "Development Status :: 2 - Pre-Alpha",
32 | "Intended Audience :: Developers",
33 | "License :: OSI Approved :: BSD License",
34 | "Natural Language :: English",
35 | "Programming Language :: Python :: 3",
36 | "Programming Language :: Python :: 3.8",
37 | ],
38 | )
39 |
--------------------------------------------------------------------------------
/testpypi_install_bacpypes3.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python3 -m pip install --upgrade \
4 | --index-url https://test.pypi.org/simple/ \
5 | --extra-index-url https://pypi.org/simple/ \
6 | bacpypes3
7 | python3 -c 'import bacpypes3; print("bacpypes3:", bacpypes3.__version__, bacpypes3.__file__)'
8 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | BACpypes3 Testing
6 | ----------------
7 | """
8 |
9 | from . import test__template # noqa: F401
10 | from . import test_pdu # noqa: F401
11 | from . import test_vlan # noqa: F401
12 | from . import test_utilities # noqa: F401
13 |
14 | from . import test_primitive_data # noqa: F401
15 | from . import test_basetypes # noqa: F401
16 | from . import test_constructed_data # noqa: F401
17 |
--------------------------------------------------------------------------------
/tests/clocked_test.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import asyncio
4 | import pytest
5 |
6 | from typing import Any, Optional, AsyncGenerator, cast
7 |
8 |
9 | class ClockedTest:
10 | """
11 | A context wrapper around an event loop that allows tests to advance the
12 | clock which is inspired by the ClockedTestCase of the asynctest library (no
13 | longer receiving updates for Python 3.8+).
14 | """
15 |
16 | time: float = 0.0
17 |
18 | def __init__(self, loop: asyncio.BaseEventLoop) -> None:
19 | self.loop = loop
20 |
21 | def __enter__(self) -> ClockedTest:
22 | # incorporate offset timing into the event loop
23 | self._original_time_fn = self.loop.time
24 | self.loop.time = lambda: self.time # type: ignore[assignment]
25 | return self
26 |
27 | def __exit__(self, *_: Any, **__: Any) -> None:
28 | # restore the loop time function
29 | self.loop.time = self._original_time_fn # type: ignore[assignment]
30 |
31 | async def __call__(self, seconds: float) -> None:
32 | await self.advance(seconds)
33 |
34 | async def advance(self, seconds: float) -> None:
35 | if seconds < 0:
36 | raise ValueError("Advance time must not be negative")
37 | await self._drain_loop()
38 |
39 | target_time = self.time + seconds
40 | while True:
41 | next_time = self._next_scheduled()
42 | if next_time is None or next_time > target_time:
43 | break
44 |
45 | self.time = next_time
46 | await self._drain_loop()
47 |
48 | self.time = target_time
49 | await self._drain_loop()
50 |
51 | def _next_scheduled(self) -> Optional[float]:
52 | try:
53 | return cast(float, self.loop._scheduled[0]._when) # type: ignore[attr-defined]
54 | except IndexError:
55 | return None
56 |
57 | async def _drain_loop(self) -> None:
58 | while True:
59 | next_time = self._next_scheduled()
60 | if not self.loop._ready and ( # type: ignore[attr-defined]
61 | next_time is None or next_time > self.time
62 | ):
63 | break
64 | await asyncio.sleep(0)
65 |
66 |
67 | @pytest.fixture(scope="function")
68 | async def clocked_test() -> AsyncGenerator[ClockedTest, None]:
69 | """
70 | This function scoped fixture returns an instance of a ClockedTest.
71 | """
72 | with ClockedTest(asyncio.get_running_loop()) as clocked_test:
73 | yield clocked_test
74 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Glue routines to simulate package setup and teardown.
5 | """
6 |
7 | import _pytest # type: ignore[import]
8 |
9 | from .utilities import setup_package, teardown_package
10 | from .clocked_test import clocked_test # noqa: F401
11 |
12 |
13 | def pytest_configure(config: _pytest.config.Config) -> None:
14 | setup_package()
15 |
16 |
17 | def pytest_unconfigure() -> None:
18 | teardown_package()
19 |
--------------------------------------------------------------------------------
/tests/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | mypy_path = ..
3 | show_error_codes = True
4 |
--------------------------------------------------------------------------------
/tests/test_1.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | test_something
6 | --------------
7 | """
8 |
9 | import unittest
10 | from typing import Callable
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 |
14 | # some debugging
15 | _debug = 0
16 | _log = ModuleLogger(globals())
17 |
18 |
19 | @bacpypes_debugging
20 | class TestSomething(unittest.TestCase):
21 |
22 | _debug: Callable[..., None]
23 |
24 | def setUp(self) -> None:
25 | if _debug:
26 | TestSomething._debug("setUp")
27 |
28 | def test_something(self) -> None:
29 | if _debug:
30 | TestSomething._debug("test_something")
31 |
32 | def tearDown(self) -> None:
33 | if _debug:
34 | TestSomething._debug("tearDown")
35 |
--------------------------------------------------------------------------------
/tests/test__template.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Module Template
6 | --------------------
7 | """
8 |
9 | import unittest
10 |
11 | from typing import Callable, Any
12 |
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def setup_module() -> None:
22 | """This function is called once at the beginning of all of the tests
23 | in this module."""
24 | if _debug:
25 | setup_module._debug("setup_module") # type: ignore[attr-defined]
26 |
27 |
28 | @bacpypes_debugging
29 | def teardown_module() -> None:
30 | """This function is called once at the end of the tests in this module."""
31 | if _debug:
32 | teardown_module._debug("teardown_module") # type: ignore[attr-defined]
33 |
34 |
35 | @bacpypes_debugging
36 | def setup_function(function: Callable[..., Any]) -> None:
37 | """This function is called before each module level test function."""
38 | if _debug:
39 | setup_function._debug( # type: ignore[attr-defined]
40 | "setup_function %r", function
41 | )
42 |
43 |
44 | @bacpypes_debugging
45 | def teardown_function(function: Callable[..., Any]) -> None:
46 | """This function is called after each module level test function."""
47 | if _debug:
48 | teardown_function._debug( # type: ignore[attr-defined]
49 | "teardown_function %r", function
50 | )
51 |
52 |
53 | @bacpypes_debugging
54 | def test_some_function(*args: Any, **kwargs: Any) -> None:
55 | """This is a module level test function."""
56 | if _debug:
57 | setup_function._debug( # type: ignore[attr-defined]
58 | "test_some_function %r %r", args, kwargs
59 | )
60 |
61 |
62 | @bacpypes_debugging
63 | class TestCaseTemplate(unittest.TestCase):
64 |
65 | _debug: Callable[..., None]
66 |
67 | @classmethod
68 | def setup_class(cls) -> None:
69 | """This function is called once before the test case is instantiated
70 | for each of the tests."""
71 | if _debug:
72 | TestCaseTemplate._debug("setup_class")
73 |
74 | @classmethod
75 | def teardown_class(cls) -> None:
76 | """This function is called once at the end after the last instance
77 | of the test case has been abandon."""
78 | if _debug:
79 | TestCaseTemplate._debug("teardown_class")
80 |
81 | def setup_method(self, method: Callable[..., Any]) -> None:
82 | """This function is called before each test method is called as is
83 | given a reference to the test method."""
84 | if _debug:
85 | TestCaseTemplate._debug("setup_method %r", method)
86 |
87 | def teardown_method(self, method: Callable[..., Any]) -> None:
88 | """This function is called after each test method has been called and
89 | is given a reference to the test method."""
90 | if _debug:
91 | TestCaseTemplate._debug("teardown_method %r", method)
92 |
93 | def test_something(self) -> None:
94 | """This is a method level test function."""
95 | if _debug:
96 | TestCaseTemplate._debug("test_something")
97 |
98 | def test_something_else(self) -> None:
99 | """This is another method level test function."""
100 | if _debug:
101 | TestCaseTemplate._debug("test_something_else")
102 |
--------------------------------------------------------------------------------
/tests/test_app/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Test BACpypes3 Application Module
3 | """
4 |
5 | from . import test_parse_reference
6 | from . import test_read_write_api
7 |
--------------------------------------------------------------------------------
/tests/test_basetypes/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Test BACpypes Base Types Module
5 | """
6 |
7 | from . import test_datetime
--------------------------------------------------------------------------------
/tests/test_basetypes/test_datetime.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Date
6 | ---------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Date, Time
14 | from bacpypes3.basetypes import DateTime
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 |
21 | @bacpypes_debugging
22 | class TestDateTime(unittest.TestCase):
23 | def test_ctor(self):
24 | if _debug:
25 | TestDateTime._debug("test_ctor")
26 |
27 | # no ctor parameters
28 | obj = DateTime()
29 | assert obj.date is None
30 | assert obj.time is None
31 |
32 | # ctor simple parameters
33 | assert DateTime("2025-01-01") == DateTime(date=(125, 1, 1, 3), time=(0, 0, 0, 0))
34 | assert DateTime("2025-01-01 12:34:56") == DateTime(date=(125, 1, 1, 3), time=(12, 34, 56, 0))
35 | assert DateTime("2025-01-01 12:34:56.7") == DateTime(date=(125, 1, 1, 3), time=(12, 34, 56, 70))
36 | assert DateTime("2025-01-01 12:34:56.78") == DateTime(date=(125, 1, 1, 3), time=(12, 34, 56, 78))
37 | assert DateTime("2025-01-01 12:34:56.789") == DateTime(date=(125, 1, 1, 3), time=(12, 34, 56, 78))
38 |
39 | assert DateTime("2025-01-01 mon") == DateTime(date=(125, 1, 1, 1), time=(0, 0, 0, 0))
40 |
--------------------------------------------------------------------------------
/tests/test_constructed_data/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Test BACpypes Constructed Data Module
3 | """
4 |
5 | from . import test_any
6 | from . import test_array
7 | from . import test_choice
8 | from . import test_list
9 | from . import test_sequence
10 | from . import test_sequence_of
11 |
12 | from . import test_read_property_multiple
13 |
--------------------------------------------------------------------------------
/tests/test_constructed_data/test_any.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Any
6 | --------
7 | """
8 |
9 | import inspect
10 | import unittest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.primitivedata import Integer
14 | from bacpypes3.constructeddata import Any, Sequence
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 |
21 | @bacpypes_debugging
22 | def any_endec(cls, *args, **kwargs):
23 | """
24 | Encode an instance of the class using the arguments and keyword arguments,
25 | then decode the tag list using the class and confirm they are identical.
26 | """
27 | if _debug:
28 | any_endec._debug("test_endec %r %r", cls, kwargs)
29 |
30 | obj1 = cls(*args, **kwargs)
31 | if _debug:
32 | any_endec._debug(" - obj1: %r", obj1)
33 |
34 | tag_list = obj1.encode()
35 | if _debug:
36 | any_endec._debug(" - tag_list: %r", tag_list)
37 |
38 | obj2 = cls.decode(tag_list)
39 | if _debug:
40 | any_endec._debug(" - obj2: %r", obj2)
41 |
42 | assert obj1 == obj2
43 |
44 |
45 | @bacpypes_debugging
46 | class TestAny000(unittest.TestCase):
47 | def test_ctor(self):
48 | if _debug:
49 | TestAny000._debug("test_ctor")
50 |
51 | # no ctor parameters is a class
52 | cls = Any()
53 | assert inspect.isclass(cls)
54 |
55 | # context decorator still a class
56 | cls = Any(_context=1)
57 | assert inspect.isclass(cls)
58 |
59 | # pass None is uninitialized
60 | obj = Any(None)
61 | assert isinstance(obj, Any)
62 | assert obj.tagList is None
63 |
64 | def test_atomic_ctor(self):
65 | if _debug:
66 | TestAny000._debug("test_atomic_ctor")
67 |
68 | # ctor atomic parameter
69 | obj = Any(Integer(1))
70 | assert obj.tagList
71 |
72 | def test_cast(self):
73 | if _debug:
74 | TestAny000._debug("test_cast")
75 |
76 | value = Integer(2)
77 |
78 | obj1 = Any(None)
79 | obj1.cast_in(value)
80 | obj2 = obj1.cast_out(Integer)
81 | assert obj2 == value
82 |
83 | def test_copy(self):
84 | if _debug:
85 | TestAny000._debug("test_copy")
86 |
87 | obj1 = Any(Integer(1))
88 | obj2 = Any(obj1)
89 | assert obj1 == obj2
90 |
91 |
92 | #
93 | # Thing001
94 | #
95 |
96 |
97 | class Thing001(Sequence):
98 | _order = ("i",)
99 | i: Integer(_context=1)
100 |
101 |
102 | @bacpypes_debugging
103 | class TestAny001(unittest.TestCase):
104 | def test_ctor(self):
105 | if _debug:
106 | TestAny001._debug("test_ctor")
107 |
108 | # ctor nonatomic parameter
109 | obj = Any(Thing001(i=1))
110 | assert obj.tagList
111 |
112 | def test_cast(self):
113 | if _debug:
114 | TestAny001._debug("test_cast")
115 |
116 | value = Thing001(i=2)
117 |
118 | obj1 = Any(None)
119 | obj1.cast_in(value)
120 | obj2 = obj1.cast_out(Thing001)
121 | assert obj2 == value
122 |
--------------------------------------------------------------------------------
/tests/test_constructed_data/test_array.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test ArrayOf
6 | ------------
7 | """
8 |
9 | import unittest
10 |
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.primitivedata import Integer
13 | from bacpypes3.constructeddata import ArrayOf
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def array_of_endec(cls, *args, **kwargs):
22 | """
23 | Encode an instance of the class using the keyword arguments, then
24 | decode the tag list using the class and confirm they are identical.
25 | """
26 | if _debug:
27 | array_of_endec._debug("test_endec %r %r %r", cls, args, kwargs)
28 |
29 | obj1 = cls(*args, **kwargs)
30 | if _debug:
31 | array_of_endec._debug(" - obj1: %r", obj1)
32 |
33 | tag_list = obj1.encode()
34 | if _debug:
35 | array_of_endec._debug(" - tag_list: %r", tag_list)
36 |
37 | obj2 = cls.decode(tag_list)
38 | if _debug:
39 | array_of_endec._debug(" - obj2: %r", obj2)
40 |
41 | assert obj1 == obj2
42 |
43 |
44 | #
45 | # Thing000
46 | #
47 |
48 |
49 | Thing000 = ArrayOf(Integer)
50 |
51 |
52 | @bacpypes_debugging
53 | class TestThing000(unittest.TestCase):
54 | def test_ctor(self):
55 | if _debug:
56 | TestThing000._debug("test_ctor")
57 |
58 | # no ctor parameters
59 | obj = Thing000()
60 | assert len(obj) == 0
61 |
62 | # empty array
63 | obj = Thing000([])
64 | assert len(obj) == 0
65 |
66 | # single element
67 | obj = Thing000([1])
68 | assert len(obj) == 1
69 | assert isinstance(obj[0], Integer)
70 |
71 | def test_copy(self):
72 | if _debug:
73 | TestThing000._debug("test_copy")
74 |
75 | obj1 = Thing000([1, 2, 3])
76 | obj2 = Thing000(obj1)
77 | assert obj1 == obj2
78 |
79 | def test_index(self):
80 | if _debug:
81 | TestThing000._debug("test_index")
82 |
83 | # no ctor parameters
84 | obj = Thing000()
85 | assert len(obj) == 0
86 |
87 | def test_endec_1(self):
88 | if _debug:
89 | TestThing000._debug("test_endec_1")
90 |
91 | # encode and decode
92 | array_of_endec(Thing000, [4, 5])
93 |
94 | def test_extend(self):
95 | if _debug:
96 | TestThing000._debug("test_extend")
97 |
98 | # simple array
99 | obj = Thing000([1, 2, 3])
100 | assert len(obj) == 3
101 |
102 | # add an item to the end
103 | obj.append(4)
104 | assert len(obj) == 4
105 |
106 |
107 | #
108 | # Thing001
109 | #
110 |
111 |
112 | Thing001 = ArrayOf(Integer, _context=1)
113 |
114 |
115 | @bacpypes_debugging
116 | class TestThing001(unittest.TestCase):
117 | def test_endec_1(self):
118 | if _debug:
119 | TestThing001._debug("test_endec_1")
120 |
121 | # encode and decode
122 | array_of_endec(Thing001, [4, 5])
123 |
--------------------------------------------------------------------------------
/tests/test_constructed_data/test_choice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Choice
6 | -----------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.primitivedata import Integer, Real
14 | from bacpypes3.constructeddata import Choice
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 |
21 | @bacpypes_debugging
22 | def choice_endec(cls, **kwargs):
23 | """
24 | Encode an instance of the class using the keyword arguments, then
25 | decode the tag list using the class and confirm they are identical.
26 | """
27 | if _debug:
28 | choice_endec._debug("test_endec %r %r", cls, kwargs)
29 |
30 | obj1 = cls(**kwargs)
31 | if _debug:
32 | choice_endec._debug(" - obj1: %r", obj1)
33 |
34 | tag_list = obj1.encode()
35 | if _debug:
36 | choice_endec._debug(" - tag_list: %r", tag_list)
37 |
38 | obj2 = cls.decode(tag_list)
39 | if _debug:
40 | choice_endec._debug(" - obj2: %r", obj2)
41 |
42 | assert obj1 == obj2
43 |
44 |
45 | #
46 | # Thing000
47 | #
48 |
49 |
50 | class Thing000(Choice):
51 | i: Integer
52 | j: Real
53 |
54 |
55 | @bacpypes_debugging
56 | class TestThing000(unittest.TestCase):
57 | def test_ctor(self):
58 | if _debug:
59 | TestThing000._debug("test_ctor")
60 |
61 | # no ctor parameters
62 | obj = Thing000()
63 | assert obj.i is None
64 | assert obj.j is None
65 | assert obj._choice is None
66 |
67 | # set the choice
68 | obj.i = 1
69 | assert obj.i == 1
70 | assert isinstance(obj.i, Integer)
71 | assert obj._choice == "i"
72 | assert obj.j is None
73 |
74 | # change the choice
75 | obj.j = 2.5
76 | assert obj.i is None
77 | assert isinstance(obj.j, Real)
78 | assert obj._choice == "j"
79 | assert obj.j == 2.5
80 |
81 | # clear the choice
82 | obj.j = None
83 | assert obj.i is None
84 | assert obj.j is None
85 |
86 | def test_ctor_kwarg(self):
87 | if _debug:
88 | TestThing000._debug("test_ctor_kwarg")
89 |
90 | # pick a parameter
91 | obj = Thing000(i=2)
92 | assert obj.i == 2
93 | assert obj._choice == "i"
94 |
95 | # multiple parameters an error
96 | with pytest.raises(RuntimeError):
97 | Thing000(i=2, j=3.5)
98 |
99 | def test_ctor_dict(self):
100 | if _debug:
101 | TestThing000._debug("test_ctor_dict")
102 |
103 | # pick a parameter
104 | obj = Thing000({"i": 3})
105 | assert obj.i == 3
106 | assert obj._choice == "i"
107 |
108 | # wrong attribute
109 | with pytest.raises(AttributeError):
110 | Thing000({"k": 4})
111 |
112 | # multiple parameters an error
113 | with pytest.raises(RuntimeError):
114 | Thing000({"i": 4, "j": 5.5})
115 |
116 | def test_copy(self):
117 | if _debug:
118 | TestThing000._debug("test_copy")
119 |
120 | obj1 = Thing000(i=1)
121 | obj2 = Thing000(obj1)
122 | assert obj1 == obj2
123 |
124 | def test_endec_1(self):
125 | if _debug:
126 | TestThing000._debug("test_endec_1")
127 |
128 | # encode and decode
129 | choice_endec(Thing000, j=4.5)
130 |
--------------------------------------------------------------------------------
/tests/test_constructed_data/test_list.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test ListOf
6 | -----------
7 | """
8 |
9 | import unittest
10 |
11 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
12 | from bacpypes3.primitivedata import Integer
13 | from bacpypes3.constructeddata import ListOf
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def list_of_endec(cls, *args, **kwargs):
22 | """
23 | Encode an instance of the class using the keyword arguments, then
24 | decode the tag list using the class and confirm they are identical.
25 | """
26 | if _debug:
27 | list_of_endec._debug("test_endec %r %r %r", cls, args, kwargs)
28 |
29 | obj1 = cls(*args, **kwargs)
30 | if _debug:
31 | list_of_endec._debug(" - obj1: %r", obj1)
32 |
33 | tag_list = obj1.encode()
34 | if _debug:
35 | list_of_endec._debug(" - tag_list: %r", tag_list)
36 |
37 | obj2 = cls.decode(tag_list)
38 | if _debug:
39 | list_of_endec._debug(" - obj2: %r", obj2)
40 |
41 | assert obj1 == obj2
42 |
43 |
44 | #
45 | # Thing000
46 | #
47 |
48 |
49 | Thing000 = ListOf(Integer)
50 |
51 |
52 | @bacpypes_debugging
53 | class TestThing000(unittest.TestCase):
54 | def test_ctor(self):
55 | if _debug:
56 | TestThing000._debug("test_ctor")
57 |
58 | # no ctor parameters
59 | obj = Thing000()
60 | assert len(obj) == 0
61 |
62 | # empty list
63 | obj = Thing000([])
64 | assert len(obj) == 0
65 |
66 | # single element
67 | obj = Thing000([1])
68 | assert len(obj) == 1
69 | assert isinstance(obj[0], Integer)
70 |
71 | def test_copy(self):
72 | if _debug:
73 | TestThing000._debug("test_copy")
74 |
75 | obj1 = Thing000([1, 2, 3])
76 | obj2 = Thing000(obj1)
77 | assert obj1 == obj2
78 |
79 | def test_endec_1(self):
80 | if _debug:
81 | TestThing000._debug("test_endec_1")
82 |
83 | # encode and decode
84 | list_of_endec(Thing000, [4, 5])
85 |
86 |
87 | #
88 | # Thing001
89 | #
90 |
91 |
92 | Thing001 = ListOf(Integer, _context=1)
93 |
94 |
95 | @bacpypes_debugging
96 | class TestThing001(unittest.TestCase):
97 | def test_endec_1(self):
98 | if _debug:
99 | TestThing001._debug("test_endec_1")
100 |
101 | # encode and decode
102 | list_of_endec(Thing001, [4, 5])
103 |
--------------------------------------------------------------------------------
/tests/test_pdu/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Test BACpypes PDU Module
5 | """
6 |
7 | from . import test_address
8 | from . import test_pci
9 | from . import test_pdu
10 |
--------------------------------------------------------------------------------
/tests/test_pdu/test_pci.py:
--------------------------------------------------------------------------------
1 | # placeholder
2 |
--------------------------------------------------------------------------------
/tests/test_pdu/test_pdu.py:
--------------------------------------------------------------------------------
1 | # placeholder
2 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Test BACpypes Primitive Data Module
5 | """
6 |
7 | from . import test_tag
8 | from . import test_null
9 | from . import test_boolean
10 | from . import test_unsigned
11 | from . import test_integer
12 | from . import test_real
13 | from . import test_double
14 | from . import test_octet_string
15 | from . import test_character_string
16 | from . import test_bit_string
17 | from . import test_enumerated
18 | from . import test_date
19 | from . import test_time
20 | from . import test_object_identifier
21 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_boolean.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Boolean
6 | ------------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, Boolean
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def boolean_tag(x):
22 | """Convert a hex string to a null application tag."""
23 | if _debug:
24 | boolean_tag._debug("boolean_tag %r", x)
25 |
26 | tag = Tag(TagClass.application, TagNumber.boolean, x, b"")
27 | if _debug:
28 | boolean_tag._debug(" - tag: %r", tag)
29 |
30 | return tag
31 |
32 |
33 | @bacpypes_debugging
34 | def boolean_encode(obj):
35 | """Encode a Boolean object into a tag."""
36 | if _debug:
37 | boolean_encode._debug("boolean_encode %r", obj)
38 |
39 | tag = obj.encode()[0]
40 | if _debug:
41 | boolean_encode._debug(" - tag: %r", tag)
42 |
43 | return tag
44 |
45 |
46 | @bacpypes_debugging
47 | def boolean_decode(tag):
48 | """Decode a null from a tag."""
49 | if _debug:
50 | boolean_decode._debug("boolean_decode %r", tag)
51 |
52 | obj = Boolean.decode(TagList([tag]))
53 | if _debug:
54 | boolean_decode._debug(" - obj: %r", obj)
55 |
56 | return obj
57 |
58 |
59 | @bacpypes_debugging
60 | def boolean_endec(v, x):
61 | """Pass the value to Boolean, construct a tag from the hex string,
62 | and compare results of encoding and decoding."""
63 | if _debug:
64 | boolean_endec._debug("boolean_endec %r %r", v, x)
65 |
66 | obj = Boolean(v)
67 | if _debug:
68 | boolean_endec._debug(" - obj: %r", obj)
69 |
70 | tag = boolean_tag(x)
71 | if _debug:
72 | boolean_endec._debug(" - tag: %r, %r", tag, tag.tag_data)
73 |
74 | assert boolean_encode(obj) == tag
75 | assert boolean_decode(tag) == obj
76 |
77 |
78 | @bacpypes_debugging
79 | class TestBoolean(unittest.TestCase):
80 | def test_boolean(self):
81 | if _debug:
82 | TestBoolean._debug("test_boolean")
83 |
84 | # booleans cannot extend bool, so they are int values 1 or 0
85 | obj = Boolean(0)
86 | assert obj == 0
87 |
88 | with pytest.raises(ValueError):
89 | Boolean("some string")
90 | with pytest.raises(TypeError):
91 | Boolean(1.0)
92 |
93 | def test_boolean_value(self):
94 | if _debug:
95 | TestBoolean._debug("test_boolean_value")
96 |
97 | obj = Boolean(True)
98 | assert obj == 1
99 |
100 | def test_boolean_copy(self):
101 | if _debug:
102 | TestBoolean._debug("test_boolean_copy")
103 |
104 | obj1 = Boolean(0)
105 | obj2 = Boolean(obj1)
106 | assert obj1 == obj2
107 |
108 | def test_boolean_endec(self):
109 | if _debug:
110 | TestBoolean._debug("test_boolean_endec")
111 |
112 | boolean_endec(False, 0)
113 | boolean_endec(True, 1)
114 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_double.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Double
6 | -----------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, Double
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def double_tag(x):
22 | """Convert a hex string to a real application tag."""
23 | if _debug:
24 | double_tag._debug("double_tag %r", x)
25 |
26 | b = xtob(x)
27 | tag = Tag(TagClass.application, TagNumber.double, len(b), b)
28 | if _debug:
29 | double_tag._debug(" - tag: %r", tag)
30 |
31 | return tag
32 |
33 |
34 | @bacpypes_debugging
35 | def double_encode(obj):
36 | """Encode a Double object into a tag."""
37 | if _debug:
38 | double_encode._debug("double_encode %r", obj)
39 |
40 | tag = obj.encode()[0]
41 | if _debug:
42 | double_encode._debug(" - tag: %r", tag)
43 |
44 | return tag
45 |
46 |
47 | @bacpypes_debugging
48 | def double_decode(tag):
49 | """Decode a Double from a tag."""
50 | if _debug:
51 | double_decode._debug("double_decode %r", tag)
52 |
53 | obj = Double.decode(TagList([tag]))
54 | if _debug:
55 | double_decode._debug(" - obj: %r", obj)
56 |
57 | return obj
58 |
59 |
60 | @bacpypes_debugging
61 | def double_endec(v, x):
62 | """Pass the value to Double, construct a tag from the hex string,
63 | and compare results of encoding and decoding."""
64 | if _debug:
65 | double_endec._debug("double_endec %r %r", v, x)
66 |
67 | obj = Double(v)
68 | if _debug:
69 | double_endec._debug(" - obj: %r", obj)
70 |
71 | tag = double_tag(x)
72 | if _debug:
73 | double_endec._debug(" - tag: %r", tag)
74 | tag.debug_contents()
75 |
76 | assert double_encode(obj) == tag
77 | assert double_decode(tag) == obj
78 |
79 |
80 | @bacpypes_debugging
81 | class TestDouble(unittest.TestCase):
82 | def test_double(self):
83 | if _debug:
84 | TestDouble._debug("test_double")
85 |
86 | obj = Double(0.0)
87 | assert obj == 0.0
88 |
89 | obj = Double(1.5)
90 | assert obj == 1.5
91 |
92 | obj = Double("2.5")
93 | assert obj == 2.5
94 |
95 | with pytest.raises(ValueError):
96 | Double("some string")
97 |
98 | def test_double_copy(self):
99 | if _debug:
100 | TestDouble._debug("test_double_copy")
101 |
102 | obj1 = Double(3.4)
103 | obj2 = Double(obj1)
104 | assert obj1 == obj2
105 |
106 | def test_double_endec(self):
107 | if _debug:
108 | TestDouble._debug("test_double_endec")
109 |
110 | double_endec(0, "0000000000000000")
111 | double_endec(1, "3ff0000000000000")
112 | double_endec(-1, "bff0000000000000")
113 |
114 | double_endec(73.5, "4052600000000000")
115 |
116 | inf = float("inf")
117 | double_endec(inf, "7ff0000000000000")
118 | double_endec(-inf, "fff0000000000000")
119 |
120 | # nan not the same nan
121 | # nan = float('nan')
122 | # double_endec(nan, '7ff8000000000000')
123 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_integer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Integer
6 | ------------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, Integer
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def integer_tag(x):
22 | """Convert a hex string to an integer application tag."""
23 | if _debug:
24 | integer_tag._debug("integer_tag %r", x)
25 |
26 | b = xtob(x)
27 | tag = Tag(TagClass.application, TagNumber.integer, len(b), b)
28 | if _debug:
29 | integer_tag._debug(" - tag: %r", tag)
30 |
31 | return tag
32 |
33 |
34 | @bacpypes_debugging
35 | def integer_encode(obj):
36 | """Encode a Integer object into a tag."""
37 | if _debug:
38 | integer_encode._debug("integer_encode %r", obj)
39 |
40 | tag = obj.encode()[0]
41 | if _debug:
42 | integer_encode._debug(" - tag: %r", tag)
43 |
44 | return tag
45 |
46 |
47 | @bacpypes_debugging
48 | def integer_decode(tag):
49 | """Decode a null from a tag."""
50 | if _debug:
51 | integer_decode._debug("integer_decode %r", tag)
52 |
53 | obj = Integer.decode(TagList([tag]))
54 | if _debug:
55 | integer_decode._debug(" - obj: %r", obj)
56 |
57 | return obj
58 |
59 |
60 | @bacpypes_debugging
61 | def integer_endec(v, x):
62 | """Pass the value to Integer, construct a tag from the hex string,
63 | and compare results of encoding and decoding."""
64 | if _debug:
65 | integer_endec._debug("integer_endec %r %r", v, x)
66 |
67 | obj = Integer(v)
68 | if _debug:
69 | integer_endec._debug(" - obj: %r", obj)
70 |
71 | tag = integer_tag(x)
72 | if _debug:
73 | integer_endec._debug(" - tag: %r", tag)
74 |
75 | assert integer_encode(obj) == tag
76 | assert integer_decode(tag) == obj
77 |
78 |
79 | @bacpypes_debugging
80 | class TestInteger(unittest.TestCase):
81 | def test_integer(self):
82 | if _debug:
83 | TestInteger._debug("test_integer")
84 |
85 | obj = Integer(0)
86 | assert obj == 0
87 |
88 | with pytest.raises(ValueError):
89 | Integer("some string")
90 | with pytest.raises(TypeError):
91 | Integer(1.0)
92 |
93 | def test_integer_value(self):
94 | if _debug:
95 | TestInteger._debug("test_integer_value")
96 |
97 | obj = Integer(1)
98 | assert obj == 1
99 |
100 | def test_integer_copy(self):
101 | if _debug:
102 | TestInteger._debug("test_integer_copy")
103 |
104 | obj1 = Integer(2)
105 | obj2 = Integer(obj1)
106 | assert obj1 == obj2
107 |
108 | def test_integer_endec(self):
109 | if _debug:
110 | TestInteger._debug("test_integer_endec")
111 |
112 | integer_endec(0, "00")
113 | integer_endec(1, "01")
114 | integer_endec(127, "7F")
115 | integer_endec(-1, "FF")
116 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_null.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Null
6 | ---------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, Null
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def null_tag(x):
22 | """Convert a hex string to a null application tag."""
23 | if _debug:
24 | null_tag._debug("null_tag %r", x)
25 |
26 | b = xtob(x)
27 | tag = Tag(TagClass.application, TagNumber.null, len(b), b)
28 | if _debug:
29 | null_tag._debug(" - tag: %r", tag)
30 |
31 | return tag
32 |
33 |
34 | @bacpypes_debugging
35 | def null_encode(obj):
36 | """Encode a Null object into a tag."""
37 | if _debug:
38 | null_encode._debug("null_encode %r", obj)
39 |
40 | tag = obj.encode()[0]
41 | if _debug:
42 | null_encode._debug(" - tag: %r", tag)
43 |
44 | return tag
45 |
46 |
47 | @bacpypes_debugging
48 | def null_decode(tag):
49 | """Decode a null from a tag."""
50 | if _debug:
51 | null_decode._debug("null_decode %r", tag)
52 |
53 | obj = Null.decode(TagList([tag]))
54 | if _debug:
55 | null_decode._debug(" - obj: %r", obj)
56 |
57 | return obj
58 |
59 |
60 | @bacpypes_debugging
61 | def null_endec(v, x):
62 | """Pass the value to Null, construct a tag from the hex string,
63 | and compare results of encoding and decoding."""
64 | if _debug:
65 | null_endec._debug("null_endec %r %r", v, x)
66 |
67 | obj = Null(v)
68 | if _debug:
69 | null_endec._debug(" - obj: %r", obj)
70 |
71 | tag = null_tag(x)
72 | if _debug:
73 | null_endec._debug(" - tag: %r, %r", tag, tag.tag_data)
74 |
75 | assert null_encode(obj) == tag
76 | assert null_decode(tag) == obj
77 |
78 |
79 | @bacpypes_debugging
80 | class TestNull(unittest.TestCase):
81 | def test_null(self):
82 | if _debug:
83 | TestNull._debug("test_null")
84 |
85 | obj = Null(())
86 | assert obj == ()
87 |
88 | obj = Null([])
89 | assert obj == ()
90 |
91 | obj = Null("null")
92 | assert obj == ()
93 |
94 | with pytest.raises(ValueError):
95 | Null("some string")
96 | with pytest.raises(TypeError):
97 | Null(1.0)
98 |
99 | def test_null_copy(self):
100 | if _debug:
101 | TestNull._debug("test_null_copy")
102 |
103 | obj1 = Null(())
104 | obj2 = Null(obj1)
105 | assert obj2 == ()
106 |
107 | def test_null_endec(self):
108 | if _debug:
109 | TestNull._debug("test_null_endec")
110 |
111 | null_endec((), "")
112 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_octet_string.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test OctetString
6 | ----------------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, OctetString
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def octet_string_tag(x):
22 | """Convert a hex string to an octet string application tag."""
23 | if _debug:
24 | octet_string_tag._debug("octet_string_tag %r", x)
25 |
26 | b = xtob(x)
27 | tag = Tag(TagClass.application, TagNumber.octetString, len(b), b)
28 | if _debug:
29 | octet_string_tag._debug(" - tag: %r", tag)
30 |
31 | return tag
32 |
33 |
34 | @bacpypes_debugging
35 | def octet_string_encode(obj):
36 | """Encode a OctetString object into a tag."""
37 | if _debug:
38 | octet_string_encode._debug("octet_string_encode %r", obj)
39 |
40 | tag = obj.encode()[0]
41 | if _debug:
42 | octet_string_encode._debug(" - tag: %r", tag)
43 |
44 | return tag
45 |
46 |
47 | @bacpypes_debugging
48 | def octet_string_decode(tag):
49 | """Decode a OctetString from a tag."""
50 | if _debug:
51 | octet_string_decode._debug("octet_string_decode %r", tag)
52 |
53 | obj = OctetString.decode(TagList([tag]))
54 | if _debug:
55 | octet_string_decode._debug(" - obj: %r", obj)
56 |
57 | return obj
58 |
59 |
60 | @bacpypes_debugging
61 | def octet_string_endec(x):
62 | """Pass the value to OctetString, construct a tag from the hex string,
63 | and compare results of encoding and decoding."""
64 | if _debug:
65 | octet_string_endec._debug("octet_string_endec %r", x)
66 |
67 | b = xtob(x)
68 | obj = OctetString(b)
69 | if _debug:
70 | octet_string_endec._debug(" - obj: %r", obj)
71 |
72 | tag = octet_string_tag(x)
73 | if _debug:
74 | octet_string_endec._debug(" - tag: %r", tag)
75 | tag.debug_contents()
76 |
77 | assert octet_string_encode(obj) == tag
78 | assert octet_string_decode(tag) == obj
79 |
80 |
81 | @bacpypes_debugging
82 | class TestOctetString(unittest.TestCase):
83 | def test_octet_string(self):
84 | if _debug:
85 | TestOctetString._debug("test_octet_string")
86 |
87 | obj = OctetString(b"")
88 | assert obj == b""
89 |
90 | with pytest.raises(TypeError):
91 | OctetString(1)
92 | with pytest.raises(TypeError):
93 | OctetString("some string")
94 |
95 | def test_octet_string_copy(self):
96 | if _debug:
97 | TestOctetString._debug("test_octet_string_copy")
98 |
99 | obj1 = OctetString(b"123")
100 | obj2 = OctetString(obj1)
101 | assert obj1 == obj2
102 |
103 | def test_octet_string_endec(self):
104 | if _debug:
105 | TestOctetString._debug("test_octet_string_endec")
106 |
107 | octet_string_endec("")
108 | octet_string_endec("01")
109 | octet_string_endec("0102")
110 | octet_string_endec("010203")
111 | octet_string_endec("01020304")
112 |
--------------------------------------------------------------------------------
/tests/test_primitive_data/test_real.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Test Real
6 | ---------
7 | """
8 |
9 | import unittest
10 | import pytest
11 |
12 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger, xtob
13 | from bacpypes3.primitivedata import Tag, TagClass, TagNumber, TagList, Real
14 |
15 | # some debugging
16 | _debug = 0
17 | _log = ModuleLogger(globals())
18 |
19 |
20 | @bacpypes_debugging
21 | def real_tag(x):
22 | """Convert a hex string to a real application tag."""
23 | if _debug:
24 | real_tag._debug("real_tag %r", x)
25 |
26 | b = xtob(x)
27 | tag = Tag(TagClass.application, TagNumber.real, len(b), b)
28 | if _debug:
29 | real_tag._debug(" - tag: %r", tag)
30 |
31 | return tag
32 |
33 |
34 | @bacpypes_debugging
35 | def real_encode(obj):
36 | """Encode a Real object into a tag."""
37 | if _debug:
38 | real_encode._debug("real_encode %r", obj)
39 |
40 | tag = obj.encode()[0]
41 | if _debug:
42 | real_encode._debug(" - tag: %r", tag)
43 |
44 | return tag
45 |
46 |
47 | @bacpypes_debugging
48 | def real_decode(tag):
49 | """Decode a Real from a tag."""
50 | if _debug:
51 | real_decode._debug("real_decode %r", tag)
52 |
53 | obj = Real.decode(TagList([tag]))
54 | if _debug:
55 | real_decode._debug(" - obj: %r", obj)
56 |
57 | return obj
58 |
59 |
60 | @bacpypes_debugging
61 | def real_endec(v, x):
62 | """Pass the value to Real, construct a tag from the hex string,
63 | and compare results of encoding and decoding."""
64 | if _debug:
65 | real_endec._debug("real_endec %r %r", v, x)
66 |
67 | obj = Real(v)
68 | if _debug:
69 | real_endec._debug(" - obj: %r", obj)
70 |
71 | tag = real_tag(x)
72 | if _debug:
73 | real_endec._debug(" - tag: %r", tag)
74 | tag.debug_contents()
75 |
76 | assert real_encode(obj) == tag
77 | assert real_decode(tag) == obj
78 |
79 |
80 | @bacpypes_debugging
81 | class TestReal(unittest.TestCase):
82 | def test_unsigned(self):
83 | if _debug:
84 | TestReal._debug("test_unsigned")
85 |
86 | obj = Real(0.0)
87 | assert obj == 0
88 |
89 | obj = Real(1.5)
90 | assert obj == 1.5
91 |
92 | obj = Real("2.5")
93 | assert obj == 2.5
94 |
95 | with pytest.raises(ValueError):
96 | Real("some string")
97 |
98 | def test_real_value(self):
99 | if _debug:
100 | TestReal._debug("test_real_value")
101 |
102 | obj = Real(1)
103 | assert obj == 1
104 |
105 | def test_real_copy(self):
106 | if _debug:
107 | TestReal._debug("test_real_copy")
108 |
109 | obj1 = Real(4.6)
110 | obj2 = Real(obj1)
111 | assert obj1 == obj2
112 |
113 | def test_real_endec(self):
114 | if _debug:
115 | TestReal._debug("test_real_endec")
116 |
117 | real_endec(0, "00000000")
118 | real_endec(1, "3f800000")
119 | real_endec(-1, "bf800000")
120 |
121 | real_endec(73.5, "42930000")
122 |
123 | inf = float("inf")
124 | real_endec(inf, "7f800000")
125 | real_endec(-inf, "ff800000")
126 |
127 | # nan not the same nan
128 | # nan = float('nan')
129 | # real_endec(nan, '7fc00000')
130 |
--------------------------------------------------------------------------------
/tests/test_utilities/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Test Utilities
5 | --------------
6 |
7 | This module tests the test utilities.
8 | """
9 |
10 | from . import test_state_machine
11 |
--------------------------------------------------------------------------------
/tests/test_vlan/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | Test VLAN Networking
5 | --------------------
6 |
7 | This module tests the VLAN networking.
8 | """
9 |
10 | from . import test_network
11 | from . import test_ipv4_network
12 | from . import test_ipv4_router
13 |
--------------------------------------------------------------------------------
/tests/utilities.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | """
4 | BACpypes Testing Utilities
5 | --------------------------
6 | """
7 |
8 | import os
9 |
10 | from typing import Callable, cast
11 |
12 | from bacpypes3.settings import os_settings
13 | from bacpypes3.debugging import bacpypes_debugging, ModuleLogger
14 | from bacpypes3.argparse import ArgumentParser
15 |
16 | # some debugging
17 | _debug = 0
18 | _log = ModuleLogger(globals())
19 |
20 | # defaults for testing
21 | BACPYPES_TEST = ""
22 | BACPYPES_TEST_OPTION = ""
23 |
24 | # parsed test options
25 | test_options = None
26 |
27 |
28 | @bacpypes_debugging
29 | def setup_package() -> None:
30 | fn_debug = cast(Callable[..., None], setup_package._debug) # type: ignore[attr-defined]
31 | global test_options
32 |
33 | # get the os settings
34 | os_settings()
35 |
36 | # create an argument parser
37 | parser = ArgumentParser(description=__doc__)
38 |
39 | # add an option
40 | parser.add_argument(
41 | "--option",
42 | help="this is an option",
43 | default=os.getenv("BACPYPES_TEST_OPTION") or BACPYPES_TEST_OPTION,
44 | )
45 |
46 | # get the debugging args and parse them
47 | arg_str = os.getenv("BACPYPES_TEST") or BACPYPES_TEST
48 | test_options = parser.parse_args(arg_str.split())
49 |
50 | if _debug:
51 | fn_debug("setup_package")
52 | fn_debug(" - test_options: %r", test_options)
53 |
54 |
55 | @bacpypes_debugging
56 | def teardown_package() -> None:
57 | fn_debug = cast(Callable[..., None], teardown_package._debug) # type: ignore[attr-defined]
58 |
59 | if _debug:
60 | fn_debug("teardown_package")
61 |
--------------------------------------------------------------------------------