"""BACnet Multi-State object types per ASHRAE 135-2016 Clause 12.18-12.20."""
from __future__ import annotations
from typing import Any, ClassVar
from bac_py.objects.base import (
BACnetObject,
PropertyAccess,
PropertyDefinition,
commandable_properties,
intrinsic_reporting_properties,
register_object_type,
standard_properties,
status_properties,
)
from bac_py.services.errors import BACnetError
from bac_py.types.enums import (
ErrorClass,
ErrorCode,
EventType,
ObjectType,
PropertyIdentifier,
)
class _MultiStateBase(BACnetObject):
"""Shared validation for multi-state object types.
Validates that Present_Value is within 1..Number_Of_States
on writes per Clause 12.18/12.19/12.20 and that
Number_Of_States >= 1.
"""
def write_property(
self,
prop_id: PropertyIdentifier,
value: Any,
priority: int | None = None,
array_index: int | None = None,
) -> None:
if prop_id == PropertyIdentifier.PRESENT_VALUE and isinstance(value, int):
num_states = self._properties.get(PropertyIdentifier.NUMBER_OF_STATES)
if num_states is not None and (value < 1 or value > num_states):
raise BACnetError(ErrorClass.PROPERTY, ErrorCode.VALUE_OUT_OF_RANGE)
if prop_id == PropertyIdentifier.NUMBER_OF_STATES and isinstance(value, int) and value < 1:
raise BACnetError(ErrorClass.PROPERTY, ErrorCode.VALUE_OUT_OF_RANGE)
super().write_property(prop_id, value, priority, array_index)
[docs]
@register_object_type
class MultiStateOutputObject(_MultiStateBase):
"""BACnet Multi-State Output object (Clause 12.19).
Represents an enumerated actuator command with N possible states.
Always commandable with a 16-level priority array.
Present_Value is a 1-based unsigned integer.
"""
OBJECT_TYPE: ClassVar[ObjectType] = ObjectType.MULTI_STATE_OUTPUT
INTRINSIC_EVENT_ALGORITHM: ClassVar[EventType | None] = EventType.CHANGE_OF_STATE
PROPERTY_DEFINITIONS: ClassVar[dict[PropertyIdentifier, PropertyDefinition]] = {
**standard_properties(),
**status_properties(),
PropertyIdentifier.PRESENT_VALUE: PropertyDefinition(
PropertyIdentifier.PRESENT_VALUE,
int,
PropertyAccess.READ_WRITE,
required=True,
default=1,
),
PropertyIdentifier.DEVICE_TYPE: PropertyDefinition(
PropertyIdentifier.DEVICE_TYPE,
str,
PropertyAccess.READ_WRITE,
required=False,
),
PropertyIdentifier.NUMBER_OF_STATES: PropertyDefinition(
PropertyIdentifier.NUMBER_OF_STATES,
int,
PropertyAccess.READ_ONLY,
required=True,
),
PropertyIdentifier.STATE_TEXT: PropertyDefinition(
PropertyIdentifier.STATE_TEXT,
list,
PropertyAccess.READ_WRITE,
required=False,
),
**commandable_properties(int, 1),
**intrinsic_reporting_properties(),
}
def __init__(
self,
instance_number: int,
*,
number_of_states: int = 2,
**initial_properties: Any,
) -> None:
super().__init__(instance_number, **initial_properties)
self._set_default(PropertyIdentifier.NUMBER_OF_STATES, number_of_states)
# Always commandable
self._init_commandable(1)
self._init_status_flags()
[docs]
@register_object_type
class MultiStateValueObject(_MultiStateBase):
"""BACnet Multi-State Value object (Clause 12.20).
Represents an enumerated configuration or status value.
Optionally commandable when constructed with ``commandable=True``.
Present_Value is a 1-based unsigned integer.
"""
OBJECT_TYPE: ClassVar[ObjectType] = ObjectType.MULTI_STATE_VALUE
INTRINSIC_EVENT_ALGORITHM: ClassVar[EventType | None] = EventType.CHANGE_OF_STATE
PROPERTY_DEFINITIONS: ClassVar[dict[PropertyIdentifier, PropertyDefinition]] = {
**standard_properties(),
**status_properties(),
PropertyIdentifier.PRESENT_VALUE: PropertyDefinition(
PropertyIdentifier.PRESENT_VALUE,
int,
PropertyAccess.READ_WRITE,
required=True,
default=1,
),
PropertyIdentifier.NUMBER_OF_STATES: PropertyDefinition(
PropertyIdentifier.NUMBER_OF_STATES,
int,
PropertyAccess.READ_ONLY,
required=True,
),
PropertyIdentifier.STATE_TEXT: PropertyDefinition(
PropertyIdentifier.STATE_TEXT,
list,
PropertyAccess.READ_WRITE,
required=False,
),
**commandable_properties(int, 1, required=False),
PropertyIdentifier.FAULT_VALUES: PropertyDefinition(
PropertyIdentifier.FAULT_VALUES,
list,
PropertyAccess.READ_WRITE,
required=False,
),
PropertyIdentifier.ALARM_VALUES: PropertyDefinition(
PropertyIdentifier.ALARM_VALUES,
list,
PropertyAccess.READ_WRITE,
required=False,
),
**intrinsic_reporting_properties(),
}
def __init__(
self,
instance_number: int,
*,
commandable: bool = False,
number_of_states: int = 2,
**initial_properties: Any,
) -> None:
super().__init__(instance_number, **initial_properties)
self._set_default(PropertyIdentifier.NUMBER_OF_STATES, number_of_states)
if commandable:
self._init_commandable(1)
self._init_status_flags()