Viron Astral Pool ChlorinatorGo integration

Would it help if I recorded the “new device” registration process on the Halo & ChlorGo app to show you the setup process?

It’s all good… Its just one thing i didn’t do whilst running mitmproxy.

1 Like

There appears to be direct access (or at least code for it) that uses your local network.

The file LocalUtil.cs under HaloChlorGO.WifiHelpers appears to allow connections on port 64177.

Personally I’m torn between the flexibility of using the Bluetooth proxy (which allows co-existence with the app) and using the direct connectivity via Wifi, which should be more reliable

@bowler, for what it’s worth, @nagyOUT is making some great progress towards supporting the nextgen protocol that halo uses over BLE. By the looks of it, it should be possible to extend pychlorinator and the astralpool integration to support both the eQ and the Halo.

2 Likes

I saw this too. But looking at the function ConnectAsync, it is marked as Obsolete.
image

There is also no references to this code.

Further, if you look into the code on how it operates, it depends on mdns, which checking my network, the Halo is not advertising mdns. And most importantly, the halo is not listening on that port.

image

Indeed. Privately I’ve been digging into the connection / authentication methods via local BLE with @pbutterworth. Will have more details in the next few days.

2 Likes

Ahh, I’d completely missed the obsolete notation! Also, probably should’ve tried the direct connection.

Great news you and @pbutterworth making progress on the connection/auth methods.

1 Like

So just a small Halo BLE connectivity update from @pbutterworth and myself.

We did a thing. We’ve worked out how connectivity is done.
The Halo doesn’t have a visable Bluetooth Pairing / Access Code like the EQ does, so there’s a different means on how to obtain that. So new Pairing logic will need to be implemented.

However - we have connectivity to my halo.

Obviously thats all encrypted data, so we need to decrypt it, and then parse it.

5 Likes

Been working on the Halo parsing.

(halo) danny@rp4:~/halo $ python haloconnect.py
2024-01-18 17:31:46,459 __main__ INFO: Starting Halo queue consumer
2024-01-18 17:31:46,516 __main__ INFO: connecting to Halo...
2024-01-18 17:31:47,980 __main__ INFO: connected to Halo...
got session key 953edf87593d5fa5bede0fd71bba11e7
Turn on notifications for 45000003-98b7-4e29-a03f-160174643002
Turn on notifications for 45000003-98b7-4e29-a03f-160174643002
mac key to write f49a1dbca33d5b379fd1fbf8cbbf59b3
2024-01-18 17:31:48,173 __main__ INFO: PerformVomitAsync...
2024-01-18 17:31:48,248 __main__ INFO: ExtractName CHLORINATOR
2024-01-18 17:31:48,277 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:31:48,279 __main__ INFO: ExtractName CHLORINATOR
2024-01-18 17:31:48,294 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:31:48,354 __main__ INFO: ExtractCapabilities {'PhControlType': <PhControlTypes.Automatic: 2>, 'OrpControlType': 2, 'MinimumManualAcidSetpoint': 0, 'MinimumManualChlorineSetpoint': 0, 'MinimumOrpSetpoint': 100, 'MinimumPhSetpoint': 3.0, 'MaximumManualAcidSetpoint': 10, 'MaximumManualChlorineSetpoint': 8, 'MaximumOrpSetpoint': 800, 'MaximumPhSetpoint': 10.0, 'ChlorineControlType': <ChlorineControlTypes.Automatic: 2>}
2024-01-18 17:31:48,356 __main__ INFO: ExtractTimerSetup
2024-01-18 17:31:48,384 __main__ INFO: ExtractHeaterCapabilities {'HeaterEnabled': 0, 'FilterPumpThreeSpeed': 1, 'HeaterPumpThreeSpeed': 0, 'HeaterPumpInstalled': 0, 'HeaterPumpTimerBit': 0}
2024-01-18 17:31:48,399 __main__ INFO: ExtractSolarCapabilities vars(<pychlorinator.halo_parsers.SolarCapabilitiesCharacteristic object at 0x7f7ffb8950>)
2024-01-18 17:31:48,430 __main__ INFO: ExtractEquipmentConfig {'EquipmentEnabled': 1, 'FilterPumpMode': 1, 'ModeGPO1': 255, 'ModeGPO2': 255, 'ModeGPO3': 255, 'ModeGPO4': 255, 'ModeValve1': 0, 'ModeValve2': 255, 'ModeValve3': 255, 'ModeValve4': 255, 'ModeRelay1': 255, 'ModeRelay2': 255, 'StateBitfield': 1, 'AutoEnabledBitfield': 2015, 'Mode': <Mode.Auto: 1>, 'StateFilterPump': True, 'AutoEnabledFilterPump': True, 'GPO1_Mode': <GPOMode.NotEnabled: 255>, 'GPO1_State': False, 'GPO1_AutoEnabled': True, 'GPO2_Mode': <GPOMode.NotEnabled: 255>, 'GPO2_State': False, 'GPO2_AutoEnabled': True, 'GPO3_Mode': <GPOMode.NotEnabled: 255>, 'GPO3_State': False, 'GPO3_AutoEnabled': True, 'GPO4_Mode': <GPOMode.NotEnabled: 255>, 'GPO4_State': False, 'GPO4_AutoEnabled': True, 'Valve1_Mode': <GPOMode.Off: 0>, 'Valve1_State': False, 'Valve1_AutoEnabled': False, 'Valve2_Mode': <GPOMode.NotEnabled: 255>, 'Valve2_State': False, 'Valve2_AutoEnabled': True, 'Valve3_Mode': <GPOMode.NotEnabled: 255>, 'Valve3_State': False, 'Valve3_AutoEnabled': True, 'Valve4_Mode': <GPOMode.NotEnabled: 255>, 'Valve4_State': False, 'Valve4_AutoEnabled': True, 'Relay1_Mode': <GPOMode.NotEnabled: 255>, 'Relay1_State': False, 'Relay1_AutoEnabled': True, 'Relay2_Mode': <GPOMode.NotEnabled: 255>, 'Relay2_State': False, 'Relay2_AutoEnabled': True}
2024-01-18 17:31:48,504 __main__ INFO: ExtractSettings {'General': <GeneralValues.AcidFlushEnabled|AIEnabled|DosingEnabled|ThreeSpeedPumpEnabled|ThreeSpeedPumpEnabledReadOnly|PumpProtectEnable|UseTemperatureSensor|DisplayPH: 6092>, 'CellModel': <CellModelValues.Model_35: 2>, 'ReversalPeriod': 4, 'AIWaterTurns': 20, 'AcidPumpSize': 55, 'FilterPumpSize': 0, 'DefaultManualOnSpeed': 2}
2024-01-18 17:31:48,534 __main__ INFO: ExtractWaterVolume {'VolumeUnits': <VolumeUnitsValues.Litres: 0>, 'PoolVolume': 50000, 'SpaVolume': 3000, 'PoolLeftFilter': 13714, 'SpaLeftFilter': 6000, 'Flag': <FlagValues.PoolEnabled|248: 249>}
2024-01-18 17:31:48,549 __main__ INFO: ExtractSetPoint {'PhControlSetpoint': 7.2, 'OrpControlSetpoint': 630, 'PoolChlorineControlSetpoint': 4, 'AcidControlSetpoint': 5, 'SpaChlorineControlSetpoint': 1}
2024-01-18 17:31:48,579 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 0}
2024-01-18 17:31:48,609 __main__ INFO: ExtractMaintenanceState {'Flags': 0, 'DoseDisableTimeMins': 0, 'MaintenanceTaskState': <TaskStatesValues.NoTask: 0>, 'MaintenanceTaskReturnCode': <TaskReturnCodesValues.OK: 0>, 'TaskTimeRemaining': 0, 'ValueToDisplay': 0, 'CalibrateState': <CalibrateStatesValues.Idle: 0>, 'ModeAfterComplete': <Mode.Off: 0>, 'AcidDosingDisabled': False}
2024-01-18 17:31:48,624 __main__ INFO: ExtractTimerCapabilities
2024-01-18 17:31:48,684 __main__ INFO: ExtractTimerState
2024-01-18 17:31:48,685 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,699 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,729 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,759 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,789 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,804 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,819 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,849 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,864 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,894 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,939 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,954 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,969 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:48,999 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,014 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,044 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,074 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,104 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,134 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,135 __main__ INFO: ExtractTimerConfig
2024-01-18 17:31:49,164 __main__ INFO: ExtractLightState {'ZoneModes': b'\x00\x01\x01\x01', 'ZoneColours': b'\x00\x00\x00\x00', 'ZoneStateFlags': 0, 'LightingMode_1': <Mode.Off: 0>, 'LightingMode_2': <Mode.Auto: 1>, 'LightingMode_3': <Mode.Auto: 1>, 'LightingMode_4': <Mode.Auto: 1>, 'LightingState_1': <ZoneStateFlagsValues: 0>, 'LightingState_2': <ZoneStateFlagsValues: 0>, 'LightingState_3': <ZoneStateFlagsValues: 0>, 'LightingState_4': <ZoneStateFlagsValues: 0>, 'LightingColour_1': 0, 'LightingColour_2': 0, 'LightingColour_3': 0, 'LightingColour_4': 0}
2024-01-18 17:31:49,194 __main__ INFO: ExtractLightCapabilities {'LightingEnabled': 1, 'OnBoardLightEnabled': 1, 'Model': 0, 'NumZonesInUse': 1, 'ZoneIsMulticolourFlags': <ZoneIsMulticolourFlagsValues.Zone1IsMulticolour|Zone2IsMulticolour|Zone3IsMulticolour|Zone4IsMulticolour: 15>}
2024-01-18 17:31:49,209 __main__ INFO: ExtractLightZoneNames {'ZoneNames': b'\x00\x01\x07\x07', 'LightingZoneName_1': <ZoneNamesValues.Pool: 0>, 'LightingZoneName_2': <ZoneNamesValues.Spa: 1>, 'LightingZoneName_3': <ZoneNamesValues.Other: 7>, 'LightingZoneName_4': <ZoneNamesValues.Other: 7>}
2024-01-18 17:31:49,239 __main__ INFO: ExtractTemp {'IsFahrenheit': 0, 'TempSupports': <TempSupportsValues.BoardTemp|WaterTemp|ChloroWater: 7>, 'BoardTemp': 40.3, 'WaterTemp': 21.1, 'ChloroWater': 21.3, 'SolarWater': 0.0, 'WaterTempValid': 1, 'SolarRoof': 0.0, 'Heater': 23.0, 'TempDisplayed': <TempDisplayedValues.WaterTemp: 2>}
2024-01-18 17:31:49,269 __main__ INFO: HeaterConfigCharacteristic {'HeaterPumpEnabled': 0, 'HeaterMinPumpSpeed': <SpeedLevels.Low: 0>}
2024-01-18 17:31:49,284 __main__ INFO: ExtractHeaterState {'HeaterStatusFlag': 0, 'HeaterPumpMode': <Mode.Auto: 1>, 'HeaterMode': 0, 'HeaterSetpoint': 25, 'HeatPumpMode': <HeatpumpModeValues.Heating: 1>, 'HeaterForced': <HeaterForcedEnum.NotForced: 0>, 'HeaterForcedTimeHrs': 0, 'HeaterForcedTimeMins': 0, 'HeaterWaterTempValid': <TempValidEnum.Invalid: 0>, 'HeaterWaterTemp': 23.0, 'HeaterError': 0, 'HeaterOn': False, 'HeaterPressure': False, 'HeaterGasValve': False, 'HeaterFlame': False, 'HeaterLockout': False, 'GeneralServiceRequired': False, 'IgnitionServiceRequired': False, 'CoolingAvailable': False}
2024-01-18 17:31:49,344 __main__ INFO: ExtractHeaterCooldownState {'HeaterCooldownEventOccurredFlag': 0, 'HeaterCooldownState': 0, 'Ignore': 0, 'TargetMode': 0, 'RemainingCooldownTime': 0, 'TotalHeaterCooldownTime': 300}
2024-01-18 17:31:49,374 __main__ INFO: ExtractSolarConfig {'SolarPumpStartHR': 20, 'SolarPumpStartMin': 0, 'SolarPumpStopHR': 7, 'SolarPumpStopMin': 0, 'SolarEnableFlush': 1, 'SolarFlushTimeHR': 14, 'SolarFlushTimeMin': 0, 'Differential': 70, 'SolarEnableExclPeriod': 0}
2024-01-18 17:31:49,404 __main__ INFO: ExtractSolarState {'SolarRoofTemp': 0, 'SolarWaterTemp': 0, 'SolarTemp': 30, 'SolarSeason': 1, 'SolarMode': <Mode.Off: 0>, 'SolarFlag': 0, 'SolarRoofTempValid': <TempValidEnum.Invalid: 0>, 'SolarWaterTempValid': <TempValidEnum.Invalid: 0>, 'SolarSpecTemp': 0, 'SolarMessage': <SolarMessageValues.DisplayNothing: 0>, 'SolarPumpState': False, 'SolarFlushActive': False}
2024-01-18 17:31:49,419 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:31:49,466 __main__ INFO: ExtractEquipmentParameter {'FilterPumpSpeed': 1, 'ParameterGPO1': 0, 'ParameterGPO2': 0, 'ParameterGPO3': 0, 'ParameterGPO4': 0, 'ParameterValve1': 0, 'ParameterValve2': 0, 'ParameterValve3': 0, 'ParameterValve4': 0, 'ParameterRelay1': 0, 'ParameterRelay2': 0, 'CurrentPumpSpeed': <SpeedLevels.Medium: 1>}
2024-01-18 17:31:49,479 __main__ INFO: ExtractGPONames {'DeviceType': <GPODeviceTypeValues.Connect1: 7>, 'Index': 0, 'OutletEnabled': 0, 'GPOFunction': <GPOFunctionValues.Equipment: 0>, 'GPOName': <GPONameValues.NoName: 0>, 'GPOLightingZone': 0, 'UseTimers': 1}
2024-01-18 17:31:49,524 __main__ INFO: ExtractGPONames {'DeviceType': <GPODeviceTypeValues.Connect1: 7>, 'Index': 1, 'OutletEnabled': 0, 'GPOFunction': <GPOFunctionValues.Equipment: 0>, 'GPOName': <GPONameValues.NoName: 0>, 'GPOLightingZone': 0, 'UseTimers': 1}
2024-01-18 17:31:49,525 __main__ INFO: ExtractGPONames {'DeviceType': <GPODeviceTypeValues.Connect2: 8>, 'Index': 0, 'OutletEnabled': 1, 'GPOFunction': <GPOFunctionValues.Equipment: 0>, 'GPOName': <GPONameValues.NoName: 0>, 'GPOLightingZone': 0, 'UseTimers': 1}
2024-01-18 17:31:49,539 __main__ INFO: ExtractGPONames {'DeviceType': <GPODeviceTypeValues.Connect2: 8>, 'Index': 1, 'OutletEnabled': 1, 'GPOFunction': <GPOFunctionValues.Equipment: 0>, 'GPOName': <GPONameValues.NoName: 0>, 'GPOLightingZone': 0, 'UseTimers': 1}
2024-01-18 17:31:49,569 __main__ INFO: ExtractRelayNames {'Index': 0, 'RelayEnabled': 0, 'RelayName': <RelayNameValue.Relay1: 0>, 'RelayAction': 0, 'UseTimers': 1}
2024-01-18 17:31:49,613 __main__ INFO: ExtractValveNames {'Index': 0, 'ValveEnabled': 1, 'ValveName': <ValveNameValue.WaterFeature: 4>, 'UseTimers': 0}
2024-01-18 17:31:49,644 __main__ INFO: ExtractValveNames {'Index': 1, 'ValveEnabled': 0, 'ValveName': <ValveNameValue.Spa: 3>, 'UseTimers': 1}
2024-01-18 17:31:49,645 __main__ INFO: ExtractValveNames {'Index': 2, 'ValveEnabled': 0, 'ValveName': <ValveNameValue.NoneValue: 0>, 'UseTimers': 1}
2024-01-18 17:31:49,659 __main__ INFO: ExtractValveNames {'Index': 3, 'ValveEnabled': 0, 'ValveName': <ValveNameValue.NoneValue: 0>, 'UseTimers': 1}
2024-01-18 17:31:50,589 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:31:51,713 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:31:51,758 __main__ INFO: ExtractTemp {'IsFahrenheit': 0, 'TempSupports': <TempSupportsValues.BoardTemp|WaterTemp|ChloroWater: 7>, 'BoardTemp': 40.2, 'WaterTemp': 21.1, 'ChloroWater': 21.3, 'SolarWater': 0.0, 'WaterTempValid': 1, 'SolarRoof': 0.0, 'Heater': 23.0, 'TempDisplayed': <TempDisplayedValues.WaterTemp: 2>}
2024-01-18 17:31:52,808 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}

Upon connecting, you request all data, which in the .net code is called “PerformVomitAsync” :smiley:

Once that bulk data has transfered the Halo will then send updates as data changes.

2024-01-18 17:31:53,302 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:31:53,303 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:31:53,347 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:31:53,378 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 482}
2024-01-18 17:31:53,392 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:31:53,423 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:31:56,140 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:31:57,250 __main__ INFO: ExtractTemp {'IsFahrenheit': 0, 'TempSupports': <TempSupportsValues.BoardTemp|WaterTemp|ChloroWater: 7>, 'BoardTemp': 40.3, 'WaterTemp': 21.1, 'ChloroWater': 21.3, 'SolarWater': 0.0, 'WaterTempValid': 1, 'SolarRoof': 0.0, 'Heater': 23.0, 'TempDisplayed': <TempDisplayedValues.WaterTemp: 2>}
2024-01-18 17:31:58,419 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 0}
2024-01-18 17:31:58,433 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:31:58,435 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:31:58,465 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:31:58,495 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 482}
2024-01-18 17:31:58,537 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:31:58,539 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:32:00,625 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:02,843 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3287, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:03,577 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:32:03,578 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:32:03,638 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:32:03,698 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 482}
2024-01-18 17:32:03,742 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:32:03,743 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:32:03,953 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:05,033 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3287, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:06,175 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:08,768 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:32:08,784 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:32:08,814 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:32:08,830 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 482}
2024-01-18 17:32:08,858 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:32:08,860 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:32:09,474 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:11,693 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3287, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:13,897 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:32:13,898 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:13,913 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:32:13,942 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:32:13,973 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 482}
2024-01-18 17:32:14,002 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:32:14,003 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:32:16,105 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3287, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:16,134 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 483}
2024-01-18 17:32:16,136 __main__ INFO: ExtractWaterVolume {'VolumeUnits': <VolumeUnitsValues.Litres: 0>, 'PoolVolume': 50000, 'SpaVolume': 3000, 'PoolLeftFilter': 13531, 'SpaLeftFilter': 6000, 'Flag': <FlagValues.PoolEnabled|248: 249>}
2024-01-18 17:32:17,230 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3272, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:18,310 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3301, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2024-01-18 17:32:18,325 __main__ INFO: ExtractSettings {'General': <GeneralValues.AcidFlushEnabled|AIEnabled|DosingEnabled|ThreeSpeedPumpEnabled|ThreeSpeedPumpEnabledReadOnly|PumpProtectEnable|UseTemperatureSensor|DisplayPH|49152: 55244>, 'CellModel': <CellModelValues.Model_35: 2>, 'ReversalPeriod': 4, 'AIWaterTurns': 20, 'AcidPumpSize': 55, 'FilterPumpSize': 0, 'DefaultManualOnSpeed': 2}
2024-01-18 17:32:18,340 __main__ INFO: ExtractSetPoint {'PhControlSetpoint': 7.2, 'OrpControlSetpoint': 630, 'PoolChlorineControlSetpoint': 4, 'AcidControlSetpoint': 5, 'SpaChlorineControlSetpoint': 1}
2024-01-18 17:32:19,028 __main__ INFO: ***** Requesting Additional Stats
2024-01-18 17:32:19,030 __main__ INFO: ExtractProfile: {'DeviceType': <DeviceType.Chlorinator: 1>, 'DeviceVersion': 4, 'DeviceProtocol': <DeviceProtocol.NextGen: 2>, 'DeviceProtocolRevision': 0, 'FirmwareVersionMajor': 1, 'FirmwareVersionMinor': 3, 'BootloaderVersionMajor': 0, 'BootloaderVersionMinor': 1, 'HardwareVersion': 1, 'SerialNumber': xxxxxxx}
2024-01-18 17:32:19,060 __main__ INFO: ExtractProbeStatistics {'HighestPhMeasured': 8.7, 'LowestPhMeasured': 0.0, 'HighestOrpMeasured': 820, 'LowestOrpMeasured': 0}
2024-01-18 17:32:19,105 __main__ INFO: ExtractCellStatistics {'CellReversalCount': 17, 'CellRunningTime': 86, 'LowSaltCellRunningTime': 0, 'PreviousDaysCellLoad': 0, 'DosingPumpSecs': 407, 'FilterPumpMins': 483}
2024-01-18 17:32:19,133 __main__ INFO: ***** Sending Keep Alive
2024-01-18 17:32:19,134 __main__ INFO: ExtractPowerBoardStatistics {'PowerBoardRuntime': (61,)}
2024-01-18 17:32:19,420 __main__ INFO: ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}

Anyways, for the most part, the parses for the Halo is done.

Now to incorporate it all into Home Assistant…

There’s alot more data provided by the Halo vs the Viron/Eq. So there’s alot of work to be done.

I’ve noticed there are different fields depending if you have both ORP and PH sensors, as to if you operate in AI mode or not. Of those who have a Halo. Do you have one or both sensors? What mode do you operate it in? ie, I’ve got a ORP Setpoint, and PH Level set point visibile in my mobile app. What do you have?

ExtractSetPoint {'PhControlSetpoint': 7.2, 'OrpControlSetpoint': 630, 'PoolChlorineControlSetpoint': 4, 'AcidControlSetpoint': 5, 'SpaChlorineControlSetpoint': 1}
ExtractState {'Flags': <FlagsValues.CellOn|CellReversed: 6>, 'RealCelllevel': 4, 'CellCurrentmA': 3316, 'MainText': 1, 'SubText1Chlorine': 9, 'ORPMeasurement': 628, 'SubText2Ph': 3, 'PHMeasurement': 7.3, 'SubText3TimerInfo': 0, 'SubText3BytesData': [b'\x00\x00'], 'SubText4ErrorInfo': 0, 'Flag': 64}
2 Likes

Hey @nagyOUT - This is really excellent progress. Nice work!!!

I’ve got both the PH and ORP sensors and tend to run mine in auto mode, unless using the vacuum or creepy-crawly. In regards to the screen, I get a PH readout and a chlorine indicator as to whether it’s at the right level.

Hey @nagyOUT , great progress :slight_smile:

I operate my pump just on auto using the built-in timers, and adjust the setpoint every 4 weeks as required by water testing. I would be looking to not use the app at all and bring the control over to node-red then I can link it in to the heat pump controls on the pool also which is a separate system, but requires the pump to be on to work.

“Chlorine is ok” type message? same. Although i’ve seen a screenshot where someone’s showed the ORP Value instead. I’ve not experimented on to how to get it to display ORP instead of “Chlorine is OK”.

Is yours a ORP set point? or Chlorine set point?

This is my main interest. I want to be able to control my Heatpump based on solar power production :slight_smile:. My heatpump is on a separate pump, much like a solar water setup, So it isn’t dependant on filtration pump.

My pool install is still a work in progress, only the filtration/chlorinator is connected. The heatpump isn’t setup/connected yet to see what control I actually have from the Halo App.
Surround concrete should be poured next week. Owner builder a pool they said …

So do either of you two want to test the current state of the Parser code? I’d be interested to see the output of yours @itchyNZL given it sounds like youre using a Chlorine setpoint, instead of a ORP set point.

All you need is a later model Raspberry pi with onboard bluetooth.

Very happy to test the parser code.

Similar to you I’ve got a solar heater, so it would be great to also be able to get the Temperature readout from the Halo, so that I can drive that pump… It’s currently on a fairly basic controller, which you need to manually switch off in winter, so being able to remove that and drive the solar heater completely via HomeAssistant would be great.

Alright… I’ll do a bit of cleanup, and get some code up on github, with some basic instructions shortly.
Be prepared with your raspberry pi, and have python 3, and virtual env installed.

This is the setting I have, under setpoints and it is called Pool Chlorine Level. I don’t have any sensors to test the actual level, this is just for production I believe.

My halo has an option to plug in a heat pump and the lights, but the heat pump is on its own app, Tuya-based so I have that into HA through Tuya Local, and the lights they installed separately also so I have a smart plug on them to turn on/off.

I did discuss with the installers about getting it all going together, and they got the Halo rep out who then tried to sell the Halo Hub for 1000+ dollars…

I have a Pi from 2018, model 4 B? I also got a BT proxy for my HA setup.

Ok, I’d like to get a log from yours also. The Raspberry PI “should” work, but as i found with Bowler, he also experienced the known bleak dbus issue not passing advertisement manufacturer data info. He ended up just using a desktop/laptop with bluetooth.

I’ll send you details via a DM shortly on how what to do.

As for your heatpump intergration, yes, the Halo hub is one option, or, there’s a pcb board that you can get for the heatpump which is approx $150? or something. Which Heatpump have you got?

I have Halo via BLE in Home Assistant now…

Sure, I’ve only mapped a handful of sensors. But its a start, right?
It’s amazing just how different the Halo data recieved is to the EQ.

5 Likes

Well done!

Its alive! Pulls data, as well as gives you mode / speed control.

I’m going to get it up onto github so you can install it via hacs. There’s alot more functionality i wish to build into this. Particularly Light control, as well as heating control.

5 Likes

Ok, its ready! Just waiting for @pbutterworth to approve the merge request on the pychlorinator library with my halo additions. Will reveal github Hacs details once thats done.

This is so good @nagyOUT!! Well done on working all the message decoding kinks out :+1::+1: