Fisheye: Tag 9941beb9ed54d596b3c5b44d53f48be4b0340bb9 refers to a dead (removed) revision in file `leahi_dialin/ro/constants.py'. Fisheye: No comparison available. Pass `N' to diff? Index: leahi_dialin/ro/modules/boost_pump.py =================================================================== diff -u -rbba41bff351f24b5665d1120b2cfb53fbe04dbd7 -r9941beb9ed54d596b3c5b44d53f48be4b0340bb9 --- leahi_dialin/ro/modules/boost_pump.py (.../boost_pump.py) (revision bba41bff351f24b5665d1120b2cfb53fbe04dbd7) +++ leahi_dialin/ro/modules/boost_pump.py (.../boost_pump.py) (revision 9941beb9ed54d596b3c5b44d53f48be4b0340bb9) @@ -16,7 +16,6 @@ import struct from logging import Logger -from .constants import PUMP_CONTROL_MODE_CLOSED_LOOP, PUMP_CONTROL_MODE_OPEN_LOOP from .constants import RESET, NO_RESET from ..common.msg_defs import MsgIds, MsgFieldPositions from ..protocols.CAN import DenaliMessage, DenaliChannels Index: leahi_dialin/ro/modules/constants.py =================================================================== diff -u --- leahi_dialin/ro/modules/constants.py (revision 0) +++ leahi_dialin/ro/modules/constants.py (revision 9941beb9ed54d596b3c5b44d53f48be4b0340bb9) @@ -0,0 +1,18 @@ +########################################################################### +# +# Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file constants.py +# +# @author (last) Michael +# @date (last) 14-Nov-2024 +# @author (original) Michael +# @date (original) 14-Nov-2024 +# +############################################################################ + +RESET = 1 +NO_RESET = 0 Index: leahi_dialin/ro/modules/valves.py =================================================================== diff -u -rbba41bff351f24b5665d1120b2cfb53fbe04dbd7 -r9941beb9ed54d596b3c5b44d53f48be4b0340bb9 --- leahi_dialin/ro/modules/valves.py (.../valves.py) (revision bba41bff351f24b5665d1120b2cfb53fbe04dbd7) +++ leahi_dialin/ro/modules/valves.py (.../valves.py) (revision 9941beb9ed54d596b3c5b44d53f48be4b0340bb9) @@ -1 +1,291 @@ +########################################################################### +# +# Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. +# +# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +# +# @file valves.py +# +# @author (last) Micahel Garthwaite +# @date (last) 17-Aug-2023 +# @author (original) Peman Montazemi +# @date (original) 19-May-2020 +# +############################################################################ + +import struct +from enum import unique +from logging import Logger +from collections import OrderedDict + +from .constants import NO_RESET +from leahi_dialin.common.msg_defs import MsgIds +from leahi_dialin.protocols.CAN import DenaliMessage, DenaliChannels +from leahi_dialin.utils.base import AbstractSubSystem, publish, DialinEnum +from leahi_dialin.utils.checks import check_broadcast_interval_override_ms +from leahi_dialin.utils.conversions import integer_to_bytearray + +# Valve states +ENERGIZED = True +DEENERGIZED = False + + +@unique +class ROValveStates(DialinEnum): + VALVE_STATE_CLOSED = 0 + VALVE_STATE_OPEN = 1 + + +@unique +class ROValveNames(DialinEnum): + VWI = 0 # Valve (M4) + VFB = 1 # Valve (M7) + VFF = 2 # Valve (P6) + VPI = 3 # Valve (P11) + VCR = 4 # Valve (P33) + VCB = 5 # Valve (P34) + VCD = 6 # Valve (P37) + VROD = 7 # Valve (P39) + +class ROValves(AbstractSubSystem): + """ + Reverse Osmosis (RO) interface for valve related commands. + + """ + + # Valves states publish message field positions + START_POS_VALVES_STATES = DenaliMessage.PAYLOAD_START_INDEX + END_POS_VALVES_STATES = START_POS_VALVES_STATES + 2 # Valves States come in as a U16 value (2 bytes) + + def __init__(self, can_interface, logger: Logger): + """ + + @param can_interface: Denali CAN Messenger object + """ + + super().__init__() + self.can_interface = can_interface + self.logger = logger + self.valves_sensed_states = OrderedDict() + self.ro_valves_states_timestamp = 0.0 + + if self.can_interface is not None: + channel_id = DenaliChannels.ro_sync_broadcast_ch_id + msg_id = MsgIds.MSG_ID_RO_VALVES_STATES_DATA.value + self.can_interface.register_receiving_publication_function(channel_id, msg_id, self._handler_valves_sync) + + self.valve_states_all = 0x00000000 + self.valve_state_VWI = {"id": ROValveNames.VWI.value, "state": DEENERGIZED} + self.valve_state_VFB = {"id": ROValveNames.VFB.value, "state": DEENERGIZED} + self.valve_state_VFF = {"id": ROValveNames.VFF.value, "state": DEENERGIZED} + self.valve_state_VPI = {"id": ROValveNames.VPI.value, "state": DEENERGIZED} + self.valve_state_VCR = {"id": ROValveNames.VCR.value, "state": DEENERGIZED} + self.valve_state_VCB = {"id": ROValveNames.VCB.value, "state": DEENERGIZED} + self.valve_state_VCD = {"id": ROValveNames.VCD.value, "state": DEENERGIZED} + self.valve_state_VROD = {"id": ROValveNames.VROD.value, "state": DEENERGIZED} + + + # NOTE: The len function counts the enums with the same number only once. This is not the case in the DG valves + # class because each valve must have a unique ID. + self.valve_states_enum = [0 for _ in range(len(ROValveNames))] + + for valve in ROValveNames.__members__: + self.valves_sensed_states[valve] = '' + + def get_valve_states(self): + """ + Gets the valve states + + @return: All valve states + """ + return [ + self.valve_state_VWI.get("state", None), + self.valve_state_VFB.get("state", None), + self.valve_state_VFF.get("state", None), + self.valve_state_VPI.get("state", None), + self.valve_state_VCR.get("state", None), + self.valve_state_VCB.get("state", None), + self.valve_state_VCD.get("state", None), + self.valve_state_VROD.get("state", None), + + ] + + @staticmethod + def sort_by_id(observation): + """ + Converts a published dictionary of valve state information to an ordered list + of tuples. + + @param observation: dictionary of the observed valve states + @return: a list of tuples of the valve states + """ + + result = [] + for key, value in observation.items(): + if isinstance(value, dict): + result.append((key, value.get("id", None), value.get("state", None))) + + result = sorted(result, key=lambda each: each[1]) + return result + + @staticmethod + def _binary_to_valve_state(binary) -> bool: + """ + @param binary: binary value + @return: 1 = energized, otherwise de-energized + """ + + if binary != 0: + return ENERGIZED + else: + return DEENERGIZED + + @publish([ + "ro_valves_states_timestamp", + "valve_states_all", + "valve_state_VWI", + "valve_state_VFB", + "valve_state_VFF", + "valve_state_VPI", + "valve_state_VCR", + "valve_state_VCB", + "valve_state_VCD", + "valve_state_VROD", + "valve_states_enum" + ]) + def _handler_valves_sync(self, message, timestamp=0.0): + """ + Handles published valves states message. + + @param message: published valves states message + @return: none + """ + + vst = struct.unpack('H', bytearray(message['message'][self.START_POS_VALVES_STATES:self.END_POS_VALVES_STATES])) + # Extract each valve state from U16 valves states using bit-masking + self.valve_state_VWI["state"] = self._binary_to_valve_state(vst[0] & 1) + self.valve_state_VFB["state"] = self._binary_to_valve_state(vst[0] & 2) + self.valve_state_VFF["state"] = self._binary_to_valve_state(vst[0] & 8) + self.valve_state_VPI["state"] = self._binary_to_valve_state(vst[0] & 16) + self.valve_state_VCR["state"] = self._binary_to_valve_state(vst[0] & 32) + self.valve_state_VCB["state"] = self._binary_to_valve_state(vst[0] & 64) + self.valve_state_VCD["state"] = self._binary_to_valve_state(vst[0] & 128) + self.valve_state_VROD["state"] = self._binary_to_valve_state(vst[0] & 256) + + start = self.END_POS_VALVES_STATES + end = start + 1 + for valve_id in self.valves_sensed_states: + valve_state_number = struct.unpack('B', bytearray(message['message'][start:end]))[0] + self.valves_sensed_states[valve_id] = ROValveNames(valve_state_number).name + start = end + end += 1 + + self.ro_valves_states_timestamp = timestamp + + def cmd_valve_sensed_state_override(self, valve: int, state: bool, reset: int = NO_RESET) -> int: + """ + Constructs and sends the valve sensed state override command. + Constraints: + Must be logged into DG. + Given valve ID must be one of the valve IDs listed below. + + @param valve: unsigned int - valve ID + @param state: bool - valve state + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + ste = integer_to_bytearray(int(state)) + vlv = integer_to_bytearray(valve) + payload = rst + ste + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_ro_ch_id, + message_id=MsgIds.MSG_ID_RO_VALVE_SENSED_STATE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override valve sensed state") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_valve_override(self, valve: int, state: bool, reset: int = NO_RESET) -> int: + """ + Constructs and sends the valve state override command. + Constraints: + Must be logged into DG. + Given valve ID must be one of the valve IDs listed below. + + @param valve: unsigned int - valve ID + @param state: bool - valve state + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + """ + + rst = integer_to_bytearray(reset) + ste = integer_to_bytearray(int(state)) + vlv = integer_to_bytearray(valve) + payload = rst + ste + vlv + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_ro_ch_id, + message_id=MsgIds.MSG_ID_RO_VALVE_STATE_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("Override valve state") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content... + if received_message is not None: + # response payload is OK or not OK + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False + + def cmd_valve_broadcast_interval_override(self, ms: int, reset: int = NO_RESET) -> int: + """ + Constructs and sends the valve state override command. + Constraints: + Must be logged into DG. + Given interval must be non-zero and a multiple of the DG general task interval (50 ms). + + @param ms: unsigned int - broadcast interval (in ms) + @param reset: integer - 1 to reset a previous override, 0 to override + @return: 1 if successful, zero otherwise + + """ + + if not check_broadcast_interval_override_ms(ms): + return False + + rst = integer_to_bytearray(reset) + ivl = integer_to_bytearray(ms) + payload = rst + ivl + + message = DenaliMessage.build_message(channel_id=DenaliChannels.dialin_to_ro_ch_id, + message_id=MsgIds.MSG_ID_RO_VALVE_PUBLISH_INTERVAL_OVERRIDE_REQUEST.value, + payload=payload) + + self.logger.debug("override valves states publish interval") + + # Send message + received_message = self.can_interface.send(message) + + # If there is content in message + if received_message is not None: + # Response payload is OK or not + return received_message['message'][DenaliMessage.PAYLOAD_START_INDEX] + else: + self.logger.debug("Timeout!!!!") + return False