mirror of https://github.com/arendst/Tasmota.git
3457 lines
160 KiB
C++
3457 lines
160 KiB
C++
/*
|
||
xsns_83_neopool.ino - Sugar Valley NeoPool Control System Modbus support for Tasmota
|
||
|
||
Copyright (C) 2023 Norbert Richter
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#ifdef USE_NEOPOOL
|
||
/****************************************************************************\
|
||
* Sugar Valley NeoPool electronic pool control and water treatment system,
|
||
* also known as brand:
|
||
* Hidrolife (yellow case)
|
||
* Aquascenic (blue case)
|
||
* Oxilife (green case)
|
||
* Bionet (light blue case)
|
||
* Hidroniser (red case)
|
||
* UVScenic (lilca case)
|
||
* Station (orange case)
|
||
* Brilix
|
||
* Bayrol
|
||
* Hay
|
||
*
|
||
* Sugar Valley RS485 connector inside (DISPLAY/WIFI/EXTERN) pins
|
||
* (from top to bottom):
|
||
* ___
|
||
* 1 |* |- +12V (from internal power supply)
|
||
* 2 |* |- NC (not connected)
|
||
* 3 |* |- Modbus A+
|
||
* 4 |* |- Modbus B-
|
||
* 5 |*__|- Modbus GND
|
||
*
|
||
* Parameter: 19200 Baud / 1 Stopbit / Parity None
|
||
* Protocol: Modbus RTU
|
||
*
|
||
* Plug connector:
|
||
* The NeoPool device is a Modbus server (formerly known as a slave),
|
||
* Tasmota is a Modbus client (formerly known as a master).
|
||
* Only one Modbus client (master) can be connected to the Modbus connector
|
||
* of the same name. It is not possible to operate several clients on
|
||
* connectors with the same name.
|
||
*
|
||
* The differently labelled Modbus connectors are completely independent
|
||
* physical Modbus interfaces. Data traffic on one of the connector is
|
||
* invisible on the other connectors. One exception is the DISPLAY connector,
|
||
* which is present twice and is normally occupied by the built-in LCD.
|
||
* Since only one Modbus client can operate one Modbus server at a time, the
|
||
* DISPLAY connector is useless for our purposes as long as the internal LCD
|
||
* is connected to one of the two DISPLAY connectors at the same time.
|
||
*
|
||
* Conclusion:
|
||
* Use the WIFI or EXTERNAL connector only.
|
||
\****************************************************************************/
|
||
|
||
#define XSNS_83 83
|
||
|
||
#ifndef NEOPOOL_MODBUS_SPEED
|
||
#define NEOPOOL_MODBUS_SPEED 19200
|
||
#endif
|
||
|
||
#ifndef NEOPOOL_MODBUS_ADDRESS
|
||
#define NEOPOOL_MODBUS_ADDRESS 1 // Modbus address, "WIFI" uses 1, "EXTERN" defaults also 1
|
||
#endif
|
||
|
||
|
||
#ifndef NEOPOOL_READ_REGISTER
|
||
#define NEOPOOL_READ_REGISTER 0x04 // Function code used to read register
|
||
#endif
|
||
#ifndef NEOPOOL_WRITE_REGISTER
|
||
#define NEOPOOL_WRITE_REGISTER 0x10 // Function code used to write register
|
||
#endif
|
||
#ifndef NEOPOOL_READ_TIMEOUT
|
||
#define NEOPOOL_READ_TIMEOUT 25 // read data timeout in ms
|
||
#endif
|
||
#ifndef NEOPOOL_CACHE_INVALID_TIME
|
||
#define NEOPOOL_CACHE_INVALID_TIME 30 // data cache invalidation time in s
|
||
#endif
|
||
|
||
|
||
// Pool LED RGB lights with different programs, the individual programs can be selected
|
||
// by switching them off and on again for a defined time when the LED is switched on.
|
||
// Default timings for LED light program step sequence (NPLight 3)
|
||
#ifndef NEOPOOL_LIGHT_PRG_WAIT
|
||
#define NEOPOOL_LIGHT_PRG_WAIT 30 // delay before start prg light if light was off (in 1/10 s)
|
||
#endif
|
||
#ifndef NEOPOOL_LIGHT_PRG_DELAY
|
||
#define NEOPOOL_LIGHT_PRG_DELAY 15 // default next light prg delay (in 1/10 s)
|
||
#endif
|
||
#ifndef NEOPOOL_LIGHT_PRG_DELAY_MIN
|
||
#define NEOPOOL_LIGHT_PRG_DELAY_MIN 5 // next light prg delay min (in 1/10 s)
|
||
#endif
|
||
#ifndef NEOPOOL_LIGHT_PRG_DELAY_MAX
|
||
#define NEOPOOL_LIGHT_PRG_DELAY_MAX 100 // next light prg delay max (in 1/10 s)
|
||
#endif
|
||
|
||
#ifdef ESP32 // Defaults for ESP32 only
|
||
#ifndef NEOPOOL_RANGE_CHECKS
|
||
#define NEOPOOL_RANGE_CHECKS // Compile with value range checks
|
||
#endif
|
||
#endif
|
||
#ifdef NEOPOOL_RANGE_CHECKS
|
||
#ifndef NEOPOOL_CONNSTAT
|
||
#define NEOPOOL_CONNSTAT // Compile with connection statistics
|
||
#endif
|
||
#endif
|
||
|
||
/****************************************************************************\
|
||
* Sugar Valley Modbus Register
|
||
* (addresses marked with * are queried with each polling cycle)
|
||
* see https://downloads.vodnici.net/uploads/wpforo/attachments/69/171-Modbus-registers.pdf
|
||
*
|
||
* Register desribed with ‘!’ means the register is officially undocumented,
|
||
* the function was determined by reverse-engineering
|
||
\****************************************************************************/
|
||
enum NeoPoolRegister {
|
||
// addr Unit Description
|
||
// ------ ------ ------------------------------------------------------------
|
||
// MODBUS page (0x00xx)
|
||
// Manages general configuration of the box. This page is reserved for internal purposes
|
||
MBF_POWER_MODULE_VERSION = 0x0002, // 0x0002 ! Power module version (MSB=Major, LSB=Minor)
|
||
MBF_POWER_MODULE_NODEID = 0x0004, // 0x0004 ! Power module Node ID (6 register 0x0004 - 0x0009)
|
||
MBF_POWER_MODULE_REGISTER = 0x000C, // 0x000C ! Writing an address in this register causes the power module register address to be read out into MBF_POWER_MODULE_DATA, see MBF_POWER_MODULE_REG_*
|
||
MBF_POWER_MODULE_DATA = 0x000D, // 0x000D ! power module data as requested in MBF_POWER_MODULE_REGISTER
|
||
MBF_VOLT_24_36 = 0x0022, // 0x0022* ! Current 24-36V line in mV
|
||
MBF_VOLT_12 = 0x0023, // 0x0023* ! Current 12V line in mV
|
||
MBF_VOLT_5 = 0x006A, // 0x006A* ! 5V line in mV / 0,62069
|
||
MBF_AMP_4_20_MICRO = 0x0072, // 0x0072* ! 2-40mA line in µA * 10 (1=0,01mA)
|
||
|
||
// MEASURE page (0x01xx)
|
||
// Contains the different measurement information including hydrolysis current, pH level, redox level, etc.
|
||
MBF_ION_CURRENT = 0x0100, // 0x0100* Ionization level measured
|
||
MBF_HIDRO_CURRENT, // 0x0101* Hydrolysis intensity level
|
||
MBF_MEASURE_PH, // 0x0102* ph pH level measured in 1/100 (700 = 7.00)
|
||
MBF_MEASURE_RX, // 0x0103* mV Redox level measured in mV
|
||
MBF_MEASURE_CL, // 0x0104* ppm Chlorine level measured in 1/100 ppm (100 = 1.00 ppm)
|
||
MBF_MEASURE_CONDUCTIVITY, // 0x0105* % Conductivity level measured in %
|
||
MBF_MEASURE_TEMPERATURE, // 0x0106* °C Temperature sensor measured in 1/10° C (100 = 10.0°C)
|
||
MBF_PH_STATUS, // 0x0107* mask Status of the pH-module
|
||
MBF_RX_STATUS, // 0x0108* mask Status of the Rx-module
|
||
MBF_CL_STATUS, // 0x0109* mask Status of the Chlorine-module
|
||
MBF_CD_STATUS, // 0x010A* mask Status of the Conductivity-module
|
||
MBF_ION_STATUS = 0x010C, // 0x010C* mask Status of the Ionization-module
|
||
MBF_HIDRO_STATUS, // 0x010D* mask Status of the Hydrolysis-module
|
||
MBF_RELAY_STATE, // 0x010E* mask Status of each configurable relay
|
||
MBF_HIDRO_SWITCH_VALUE, // 0x010F* INTERNAL - contains the opening of the hydrolysis PWM.
|
||
MBF_NOTIFICATION, // 0x0110* mask Bit field that informs whether a property page has changed since the last time it was queried. (see MBMSK_NOTIF_*). This register makes it possible to refresh the content of the registers maintained by a modbus master in an optimized way, without the need to reread all registers periodically, but only those on a page that has been changed.
|
||
MBF_HIDRO_VOLTAGE, // 0x0111 The voltage applied to the hydrolysis cell. This register, together with that of MBF_HIDRO_CURRENT allows extrapolation of water salinity.
|
||
|
||
// GLOBAL page (0x02xx)
|
||
// Contains global information, such as the amount of time that each power unit has been working.
|
||
MBF_CELL_RUNTIME_LOW = 0x0206, // 0x0206* ! Cell runtime (32 bit value - low word)
|
||
MBF_CELL_RUNTIME_HIGH, // 0x0207* ! Cell runtime (32 bit value - high word)
|
||
MBF_CELL_RUNTIME_PART_LOW, // 0x0208* ! Cell part runtime (32 bit value - low word)
|
||
MBF_CELL_RUNTIME_PART_HIGH, // 0x0209* ! Cell part runtime (32 bit value - high word)
|
||
MBF_CELL_BOOST = 0x020C, // 0x020C* mask ! Boost control (see MBMSK_CELL_BOOST_*)
|
||
MBF_CELL_RUNTIME_POLA_LOW = 0x0214, // 0x0214* ! Cell runtime polarity 1 (32 bit value - low word)
|
||
MBF_CELL_RUNTIME_POLA_HIGH, // 0x0215* ! Cell runtime polarity 1 (32 bit value - high word)
|
||
MBF_CELL_RUNTIME_POLB_LOW, // 0x0216* ! Cell runtime polarity 2 (32 bit value - low word)
|
||
MBF_CELL_RUNTIME_POLB_HIGH, // 0x0217* ! Cell runtime polarity 2 (32 bit value - high word)
|
||
MBF_CELL_RUNTIME_POL_CHANGES_LOW, // 0x0218* ! Cell runtime polarity change count (32 bit value - low word)
|
||
MBF_CELL_RUNTIME_POL_CHANGES_HIGH, // 0x0219* ! Cell runtime polarity change count (32 bit value - high word)
|
||
MBF_HIDRO_MODULE_VERSION = 0x0280, // 0x0280 ! Hydrolysis module version
|
||
MBF_HIDRO_MODULE_CONNECTIVITY = 0x0281, // 0x0281 ! Hydrolysis module connection quality (in myriad: 0..10000)
|
||
MBF_SET_COUNTDOWN_KEY_AUX_OFF = 0x0287, // 0x0287 mask ! switch AUX1-4 OFF - only for AUX which is assigned as AUX and set to MBV_PAR_CTIMER_COUNTDOWN_KEY_* mode - see MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX*
|
||
MBF_SET_COUNTDOWN_KEY_AUX_ON, // 0x0288 mask ! switch AUX1-4 ON - only for AUX which is assigned as AUX and set to MBV_PAR_CTIMER_COUNTDOWN_KEY_* mode - see MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX*
|
||
MBF_SET_MANUAL_CTRL, // 0x0289 ! write a 1 before manual control MBF_RELAY_STATE, after done write 0 and do MBF_EXEC
|
||
MBF_ESCAPE = 0x0297, // 0x0297 ! A write operation to this register is the same as using the ESC button on main screen - clears error
|
||
MBF_SAVE_TO_EEPROM = 0x02F0, // 0x02F0 A write operation to this register immediately starts a EEPROM storage operation. During the EEPROM storage procedure, the system may be unresponsive to MODBUS requests. The operation will last always less than 1 second.
|
||
MBF_EXEC = 0x02F5, // 0x02F5 ! A write operation to this register immediately take over settings of the previous written data
|
||
|
||
// FACTORY page (0x03xx)
|
||
// Contains factory data such as calibration parameters for the different power units.
|
||
MBF_PAR_VERSION = 0x0300, // 0x0300* Software version of the PowerBox
|
||
MBF_PAR_MODEL, // 0x0301* mask System model options
|
||
MBF_PAR_SERNUM, // 0x0302* Serial number of the PowerBox
|
||
MBF_PAR_ION_NOM, // 0x0303* Ionization maximum production level (DO NOT WRITE!)
|
||
MBF_PAR_HIDRO_NOM = 0x0306, // 0x0306* Hydrolysis maximum production level. (DO NOT WRITE!) If the hydrolysis is set to work in percent mode, this value will be 100. If the hydrolysis module is set to work in g/h production, this module will contain the maximum amount of production in g/h units. (DO NOT WRITE!)
|
||
MBF_PAR_SAL_AMPS = 0x030A, // 0x030A Current command in regulation for which we are going to measure voltage
|
||
MBF_PAR_SAL_CELLK, // 0x030B Specifies the relationship between the resistance obtained in the measurement process and its equivalence in g / l (grams per liter)
|
||
MBF_PAR_SAL_TCOMP, // 0x030C Specifies the deviation in temperature from the conductivity.
|
||
MBF_PAR_HIDRO_MAX_VOLTAGE = 0x0322, // 0x0322 Allows setting the maximum voltage value that can be reached with the hydrolysis current regulation. The value is specified in tenths of a volt. The default value of this register when the EEPROM is cleared is 80, which is equivalent to a maximum cell operating voltage of 8 volts.
|
||
MBF_PAR_HIDRO_FLOW_SIGNAL, // 0x0323 Allows to select the operation of the flow detection signal associated with the operation of the hydrolysis (see MBV_PAR_HIDRO_FLOW_SIGNAL*). The default value for this register is 0 (standard detection).
|
||
MBF_PAR_HIDRO_MAX_PWM_STEP_UP, // 0x0324 This register sets the PWM ramp up of the hydrolysis in pulses per duty cycle. This register makes it possible to adjust the rate at which the power delivered to the cell increases, allowing a gradual rise in power so that the operation of the switching source of the equipment is not saturated. Default 150
|
||
MBF_PAR_HIDRO_MAX_PWM_STEP_DOWN, // 0x0325 This register sets the PWM down ramp of the hydrolysis in pulses per duty cycle. This register allows adjusting the rate at which the power delivered to the cell decreases, allowing a gradual drop in power so that the switched source of the equipment is not disconnected due to lack of consumption. This gradual fall must be in accordance with the type of cell used, since said cell stores charge once the current stimulus has ceased. Default 20
|
||
|
||
// INSTALLER page (0x04xx)
|
||
// Contains a set of configuration registers related to the equipment installation,#
|
||
// such as the relays used for each function, the amount of time that each pump must operate, etc.
|
||
MBF_PAR_ION_POL0 = 0x0400, // 0x0400 Time in minutes that the equipment must remain working in positive polarization in the copper-silver ionization.
|
||
MBF_PAR_ION_POL1, // 0x0401 Time in minutes that the equipment must remain working in negative polarization in the copper-silver ionization.
|
||
MBF_PAR_ION_POL2, // 0x0402 Time in minutes that the equipment must remain working in dead time (without delivering power) in the copper-silver ionization.
|
||
MBF_PAR_HIDRO_ION_CAUDAL, // 0x0403 mask Bitmask register regulates the external control mode of ionization, hydrolysis and pumps (see MBMSK_HIDRO_ION_CAUDAL_*)
|
||
MBF_PAR_HIDRO_MODE, // 0x0404 Regulates the external control mode of hydrolysis from the modules of measure. 0 = no control, 1 = standard control (on / off), 2 = with timed pump
|
||
MBF_PAR_HIDRO_POL0, // 0x0405 Time in minutes that the equipment must remain in positive polarization during hydrolysis/electrolysis.
|
||
MBF_PAR_HIDRO_POL1, // 0x0406 Time in minutes that the equipment must remain in negative polarization during hydrolysis/electrolysis.
|
||
MBF_PAR_HIDRO_POL2, // 0x0407 Time in minutes that the equipment must remain working in dead time (without delivering power) in the hydrolysis/electrolysis.
|
||
MBF_PAR_TIME_LOW, // 0x0408* System timestamp as unix timestamp (32 bit value - low word).
|
||
MBF_PAR_TIME_HIGH, // 0x0409* System timestamp as unix timestsmp (32 bit value - high word).
|
||
MBF_PAR_PH_ACID_RELAY_GPIO, // 0x040A* Relay number assigned to the acid pump function (only with pH module).
|
||
MBF_PAR_PH_BASE_RELAY_GPIO, // 0x040B* Relay number assigned to the base pump function (only with pH module).
|
||
MBF_PAR_RX_RELAY_GPIO, // 0x040C* Relay number assigned to the Redox level regulation function. If the value is 0, there is no relay assigned, and therefore there is no pump function (ON / OFF should not be displayed)
|
||
MBF_PAR_CL_RELAY_GPIO, // 0x040D* Relay number assigned to the chlorine pump function (only with free chlorine measuring modules).
|
||
MBF_PAR_CD_RELAY_GPIO, // 0x040E* Relay number assigned to the conductivity (brine) pump function (only with conductivity measurement modules).
|
||
MBF_PAR_TEMPERATURE_ACTIVE, // 0x040F* Indicates whether the equipment has a temperature measurement or not.
|
||
MBF_PAR_LIGHTING_GPIO, // 0x0410* Relay number assigned to the lighting function. 0: inactive.
|
||
MBF_PAR_FILT_MODE, // 0x0411* Filtration mode (see MBV_PAR_FILT_*)
|
||
MBF_PAR_FILT_GPIO, // 0x0412* Relay selected to perform the filtering function (by default it is relay 2). When this value is at zero, there is no relay assigned and therefore it is understood that the equipment does not control the filtration. In this case, the filter option does not appear in the user menu.
|
||
MBF_PAR_FILT_MANUAL_STATE, // 0x0413 Filtration status in manual mode (on = 1; off = 0)
|
||
MBF_PAR_HEATING_MODE, // 0x0414 Heating mode: 0 = the equipment is not heated. 1 = the equipment is heating.
|
||
MBF_PAR_HEATING_GPIO, // 0x0415 Relay number assigned to perform the heating function (by default it is relay 7). When this value is at zero, there is no relay assigned and therefore it is understood that the equipment does not control the heating. In this case, the filter modes associated with heating will not be displayed.
|
||
MBF_PAR_HEATING_TEMP, // 0x0416 Heating mode: Heating setpoint temperature
|
||
MBF_PAR_CLIMA_ONOFF, // 0x0417 Activation of the climate mode (0 = inactive, 1 = active).
|
||
MBF_PAR_SMART_TEMP_HIGH, // 0x0418 Smart mode: Upper temperature
|
||
MBF_PAR_SMART_TEMP_LOW, // 0x0419 Smart mode: Lower temperature
|
||
MBF_PAR_SMART_ANTI_FREEZE, // 0x041A Smart mode: Antifreeze mode activated (1) or not (0).
|
||
MBF_PAR_SMART_INTERVAL_REDUCTION, // 0x041B Smart mode: This register is read-only and reports to the outside what percentage (0 to 100%) is being applied to the nominal filtration time. 100% means that the total programmed time is being filtered.
|
||
MBF_PAR_INTELLIGENT_TEMP, // 0x041C Intelligent mode: Setpoint temperature
|
||
MBF_PAR_INTELLIGENT_FILT_MIN_TIME, // 0x041D Intelligent mode: Minimum filtration time in minutes
|
||
MBF_PAR_INTELLIGENT_BONUS_TIME, // 0x041E Intelligent mode: Bonus time for the current set of intervals
|
||
MBF_PAR_INTELLIGENT_TT_NEXT_INTERVAL, // 0x041F Intelligent mode: Time to next filtration interval. When it reaches 0 an interval is started and the number of seconds is reloaded for the next interval (2x3600)
|
||
MBF_PAR_INTELLIGENT_INTERVALS, // 0x0420 Intelligent mode: Number of started intervals. When it reaches 12 it is reset to 0 and the bonus time is reloaded with the value of MBF_PAR_INTELLIGENT_FILT_MIN_TIME
|
||
MBF_PAR_FILTRATION_STATE, // 0x0421 Filtration state: 0 is off and 1 is on. The filtration state is regulated according to the MBF_PAR_FILT_MANUAL_STATE register if the filtration mode held in register MBF_PAR_FILT_MODE is set to FILT_MODE_MANUAL (0).
|
||
MBF_PAR_HEATING_DELAY_TIME, // 0x0422 Timer in seconds that counts up when the heating is to be enabled. Once this counter reaches 60 seconds, the heating is then enabled. This counter is for internal use only.
|
||
MBF_PAR_FILTERING_TIME_LOW, // 0x0423 Internal timer for the intelligent filtering mode (32-bit value - low word). It counts the filtering time done during a given day. This register is only for internal use and should not be modified by the user.
|
||
MBF_PAR_FILTERING_TIME_HIGH, // 0x0424 Internal timer for the intelligent filtering mode (32-bit value - high word)
|
||
MBF_PAR_INTELLIGENT_INTERVAL_TIME_LOW, // 0x0425 Internal timer that counts the filtration interval assigned to the the intelligent mode (32-bit value - low word). This register is only for internal use and should not be modified by the user.
|
||
MBF_PAR_INTELLIGENT_INTERVAL_TIME_HIGH, // 0x0426 Internal timer that counts the filtration interval assigned to the the intelligent mode (32-bit value - high word)
|
||
MBF_PAR_UV_MODE, // 0x0427 UV mode active or not - see MBV_PAR_UV_MODE*. To enable UV support for a given device, add the mask MBMSK_MODEL_UV to the MBF_PAR_MODEL register.
|
||
MBF_PAR_UV_HIDE_WARN, // 0x0428 mask Suppression for warning messages in the UV mode (see MBMSK_UV_HIDE_WARN_*)
|
||
MBF_PAR_UV_RELAY_GPIO, // 0x0429 Relay number assigned to the UV function.
|
||
MBF_PAR_PH_PUMP_REP_TIME_ON, // 0x042A mask Time that the pH pump will be turn on in the repetitive mode (see MBMSK_PH_PUMP_*). Contains a special time format, see desc for MBMSK_PH_PUMP_TIME.
|
||
MBF_PAR_PH_PUMP_REP_TIME_OFF, // 0x042B mask Time that the pH pump will be turn off in the repetitive mode. Contains a special time format, see desc for MBMSK_PH_PUMP_TIME, has no upper configuration bit 0x8000
|
||
MBF_PAR_HIDRO_COVER_ENABLE, // 0x042C mask Options for the hydrolysis/electrolysis module (see MBMSK_HIDRO_*)
|
||
MBF_PAR_HIDRO_COVER_REDUCTION, // 0x042D Configured levels for the cover reduction and the hydrolysis shutdown temperature options: LSB = Percentage for the cover reduction, MSB = Temperature level for the hydrolysis shutdown (see MBMSK_HIDRO_*)
|
||
MBF_PAR_PUMP_RELAY_TIME_OFF, // 0x042E Time level in minutes or seconds that the dosing pump must remain off when the temporized pump mode is selected. This time level register applies to all pumps except pH. Contains a special time format, see desc for MBMSK_PH_PUMP_TIME, has no upper configuration bit 0x8000
|
||
MBF_PAR_PUMP_RELAY_TIME_ON, // 0x042F Time level in minutes or seconds that the dosing pump must remain on when the temporized pump mode is selected. This time level register applies to all pumps except pH. Contains a special time format, see desc for MBMSK_PH_PUMP_TIME, has no upper configuration bit 0x8000
|
||
MBF_PAR_RELAY_PH, // 0x0430 Determine what pH regulation configuration the equipment has (see MBV_PAR_RELAY_PH_*)
|
||
MBF_PAR_RELAY_MAX_TIME, // 0x0431 Maximum amount of time in seconds, that a dosing pump can operate before rising an alarm signal. The behavior of the system when the dosing time is exceeded is regulated by the type of action stored in the MBF_PAR_RELAY_MODE register.
|
||
MBF_PAR_RELAY_MODE, // 0x0432 Behavior of the system when the dosing time is exceeded (see MBMSK_PAR_RELAY_MODE_* and MBV_PAR_RELAY_MODE_*)
|
||
MBF_PAR_RELAY_ACTIVATION_DELAY, // 0x0433 Delay time in seconds for the pH pump when the measured pH value is outside the allowable pH setpoints. The system internally adds an extra time of 10 seconds to the value stored here. The pump starts the dosing operation once the condition of pH out of valid interval is maintained during the time specified in this register.
|
||
MBF_PAR_TIMER_BLOCK_BASE, // 0x0434 Block of 180 registers containing the configuration of the 12 timers with 15 registers each (see MBV_TIMER_OFFMB_*). The system has a set of 12 fully configurable timers, each one assigned to a specific function:
|
||
MBF_PAR_TIMER_BLOCK_FILT_INT1 = 0x0434, // 0x0434 Filtration timer 1
|
||
MBF_PAR_TIMER_BLOCK_FILT_INT2 = 0x0443, // 0x0443 Filtration tiemr 2
|
||
MBF_PAR_TIMER_BLOCK_FILT_INT3 = 0x0452, // 0x0452 Filtration timer 3
|
||
MBF_PAR_TIMER_BLOCK_AUX1_INT2 = 0x0461, // 0x0461 Aux1 timer 2
|
||
MBF_PAR_TIMER_BLOCK_LIGHT_INT = 0x0470, // 0x0470 Lighting timer
|
||
MBF_PAR_TIMER_BLOCK_AUX2_INT2 = 0x047F, // 0x047F Aux2 timer 2
|
||
MBF_PAR_TIMER_BLOCK_AUX3_INT2 = 0x048E, // 0x048E Aux3 timer 2
|
||
MBF_PAR_TIMER_BLOCK_AUX4_INT2 = 0x049D, // 0x049D Aux4 timer 2
|
||
MBF_PAR_TIMER_BLOCK_AUX1_INT1 = 0x04AC, // 0x04AC Aux1 timer 1
|
||
MBF_PAR_TIMER_BLOCK_AUX2_INT1 = 0x04BB, // 0x04BB Aux2 timer 1
|
||
MBF_PAR_TIMER_BLOCK_AUX3_INT1 = 0x04CA, // 0x04CA Aux3 timer 1
|
||
MBF_PAR_TIMER_BLOCK_AUX4_INT1 = 0x04D9, // 0x04D9 Aux4 timer 1
|
||
MBF_PAR_FILTVALVE_ENABLE = 0x04E8, // 0x04E8 Filter cleaning functionality mode (0 = off, 1 = Besgo)
|
||
MBF_PAR_FILTVALVE_MODE, // 0x04E9 Filter cleaning valve timing mode, possible modes: MBV_PAR_CTIMER_ENABLED, MBV_PAR_CTIMER_ALWAYS_ON, MBV_PAR_CTIMER_ALWAYS_OFF
|
||
MBF_PAR_FILTVALVE_GPIO, // 0x04EA Relay associated with the filter cleaning function. default AUX2 (value 5)
|
||
MBF_PAR_FILTVALVE_START_LOW, // 0x04EB Start timestamp of filter cleaning (32-bit value - low word)
|
||
MBF_PAR_FILTVALVE_START_HIGH, // 0x04EC Start timestamp of filter cleaning (32-bit value - high word)
|
||
MBF_PAR_FILTVALVE_PERIOD_MINUTES, // 0x04ED Period in minutes between cleaning actions. For example, if a value of 60 is stored in this registry, a cleanup action will occur every hour.
|
||
MBF_PAR_FILTVALVE_INTERVAL, // 0x04EE Cleaning action duration in seconds.
|
||
MBF_PAR_FILTVALVE_REMAINING, // 0x04EF Time remaining for the current cleaning action in seconds. If this register is 0, it means that there is no cleaning function running. When a cleanup function is started, the contents of the MBF_PAR_FILTVALVE_INTERVAL register are copied to this register, then decremented once per second. The display uses this log to track the progress of the cleaning function.
|
||
MBF_ACTION_COPY_TO_RTC, // 0x04F0 A write (any value) forces the writing of the RTC time registers MBF_PAR_TIME_LOW (0x0408) and MBF_PAR_TIME_HIGH (0x0409) into the RTC internal microcontroller clock management registers.
|
||
|
||
// USER page (0x05xx)
|
||
// Contains user configuration registers, such as the production level
|
||
// for the ionization and the hydrolysis, or the set points for the pH, redox, or chlorine regulation loops.
|
||
MBF_PAR_ION = 0x0500, // 0x0500* Ionization target production level. The value adjusted in this register must not exceed the value set in the MBF_PAR_ION_NOM factory register.
|
||
MBF_PAR_ION_PR, // 0x0501* Amount of time in minutes that the ionization must be activated each time that the filtration starts.
|
||
MBF_PAR_HIDRO, // 0x0502* Hydrolisis target production level. When the hydrolysis production is to be set in percent values, this value will contain the percent of production. If the hydrolysis module is set to work in g/h production, this module will contain the desired amount of production in g/h units. The value adjusted in this register must not exceed the value set in the MBF_PAR_HIDRO_NOM factory register.
|
||
MBF_PAR_PH1 = 0x0504, // 0x0504* Higher limit of the pH regulation system. The value set in this register is multiplied by 100. This means that if we want to set a value of 7.5, the numerical content that we must write in this register is 750. This register must be always higher than MBF_PAR_PH2.
|
||
MBF_PAR_PH2, // 0x0505* Lower limit of the pH regulation system. The value set in this register is multiplied by 100. This means that if we want to set a value of 7.0, the numerical content that we must write in this register is 700. This register must be always lower than MBF_PAR_PH1.
|
||
MBF_PAR_RX1 = 0x0508, // 0x0508* Set point for the redox regulation system. This value must be in the range of 0 to 1000.
|
||
MBF_PAR_CL1 = 0x050A, // 0x050A* Set point for the chlorine regulation system. The value stored in this register is multiplied by 100. This mean that if we want to set a value of 1.5 ppm, we will have to write a numerical value of 150. This value stored in this register must be in the range of 0 to 1000.
|
||
MBF_PAR_FILTRATION_CONF = 0x050F, // 0x050F* mask ! filtration type and speed, see MBMSK_PAR_FILTRATION_CONF_*
|
||
MBF_PAR_FILTRATION_SPEED_FUNC = 0x0513, // 0x0513 ! filtration speed function control
|
||
MBF_PAR_FUNCTION_DEPENDENCY = 0x051B, // 0x051B mask Specification for the dependency of different functions, such as heating, from external events like FL1 (see MBMSK_FCTDEP_HEATING/MBMSK_DEPENDENCY_*)
|
||
|
||
// MISC page (0x06xx)
|
||
// Contains the configuration parameters for the screen controllers (language, colours, sound, etc).
|
||
MBF_PAR_UICFG_MACHINE = 0x0600, // 0x0600* Machine type (see MBV_PAR_MACH_* and kNeoPoolMachineNames[])
|
||
MBF_PAR_UICFG_LANGUAGE, // 0x0601* Selected language (see MBV_PAR_LANG_*)
|
||
MBF_PAR_UICFG_BACKLIGHT, // 0x0602* Display backlight function (see MBV_PAR_BACKLIGHT_*)
|
||
MBF_PAR_UICFG_SOUND, // 0x0603* mask Audible alerts (see MBMSK_PAR_SOUND_*)
|
||
MBF_PAR_UICFG_PASSWORD, // 0x0604* System password encoded in BCD
|
||
MBF_PAR_UICFG_VISUAL_OPTIONS, // 0x0605* mask Stores the different display options for the user interface menus (bitmask). Some bits allow you to hide options that are normally visible (bits 0 to 3) while other bits allow you to show options that are normally hidden (bits 9 to 15)
|
||
MBF_PAR_UICFG_VISUAL_OPTIONS_EXT, // 0x0606* mask This register stores additional display options for the user interface menus (see MBMSK_VOE_*)
|
||
MBF_PAR_UICFG_MACH_VISUAL_STYLE, // 0x0607* mask This register is an expansion of register MBF_PAR_UICFG_MACHINE and MBF_PAR_UICFG_VISUAL_OPTIONS. If MBF_PAR_UICFG_MACHINE is MBV_PAR_MACH_GENERIC then the lower part (8 bits LSB) is used to store the type of color selected. Colors and styles correspond to those listed in MBF_PAR_UICFG_MACHINE (see MBV_PAR_MACH_*). The upper part (8-bit MSB) contains extra bits MBMSK_VS_FORCE_UNITS_GRH, MBMSK_VS_FORCE_UNITS_PERCENTAGE and MBMSK_ELECTROLISIS
|
||
MBF_PAR_UICFG_MACH_NAME_BOLD = 0x0608, // 0x0608 Machine name bold part title displayed during startup if MBF_PAR_UICFG_MACHINE is MBV_PAR_MACH_GENERIC. Note: Only lowercase letters (a-z) can be used. 4 register (0x608 to 0x60B) ASCIIZ string with up to 8 characters
|
||
MBF_PAR_UICFG_MACH_NAME_LIGHT = 0x060C, // 0x060C Machine name normal intensity part title displayed during startup if MBF_PAR_UICFG_MACHINE is MBV_PAR_MACH_GENERIC. Note: Only lowercase letters (a-z) can be used. 4 register (0x060C to 0x060F) ASCIIZ string with up to 8 characters
|
||
MBF_PAR_UICFG_MACH_NAME_AUX1 = 0x0610, // 0x0610 Aux1 relay name: 5 register ASCIIZ string with up to 10 characters
|
||
MBF_PAR_UICFG_MACH_NAME_AUX2 = 0x0615, // 0x0615 Aux2 relay name: 5 register ASCIIZ string with up to 10 characters
|
||
MBF_PAR_UICFG_MACH_NAME_AUX3 = 0x061A, // 0x061A Aux3 relay name: 5 register ASCIIZ string with up to 10 characters
|
||
MBF_PAR_UICFG_MACH_NAME_AUX4 = 0x061F, // 0x061F Aux4 relay name: 5 register ASCIIZ string with up to 10 characters
|
||
};
|
||
|
||
// Sugar Valley register constants and bit masks
|
||
enum NeoPoolConstAndBitMask {
|
||
// MBF_PH_STATUS
|
||
MBMSK_PH_STATUS_ALARM = 0x000F, // PH alarm. The possible alarm values are depending on the regulation model:
|
||
// Valid alarm values for pH regulation with acid and base:
|
||
MBV_PH_ACID_BASE_ALARM0 = 0, // no alarm
|
||
MBV_PH_ACID_BASE_ALARM1 = 1, // pH too high; the pH value is 0.8 points higher than the setpoint (PH1 on acid systems, PH2 on base systems, PH1 on acid+base systems)
|
||
MBV_PH_ACID_BASE_ALARM2 = 2, // pH too low: the pH value is 0.8 points lower than the set point value set in (PH1 on acid systems, PH2 on base systems, PH2 on acid+base systems)
|
||
MBV_PH_ACID_BASE_ALARM3 = 3, // pH pump has exceeded the working time set by the MBF_PAR_RELAY_PH_MAX_TIME parameter and has stopped.
|
||
MBV_PH_ACID_BASE_ALARM4 = 4, // pH higher than the set point (PH1 + 0.1 on acid systems, PH2 + 0.1 on base systems, PH1 on acid+base systems)
|
||
MBV_PH_ACID_BASE_ALARM5 = 5, // pH lower than the set point (PH1 - 0.3 on acid systems, PH2 - 0.3 on base systems, PH2 on acid+base systems)
|
||
MBV_PH_ACID_BASE_ALARM6 = 6, // ! tank level alarm
|
||
// bit
|
||
MBMSK_PH_STATUS_CTRL_BY_FL = 0x0400, // 10 Control status of the pH module by flow detection (if enabled by MBF_PAR_HIDRO_ION_CAUDAL)
|
||
MBMSK_PH_STATUS_ACID_PUMP_ACTIVE = 0x0800, // 11 Acid pH pump relay on (pump on)
|
||
MBMSK_PH_STATUS_BASE_PUMP_ACTIVE = 0x1000, // 12 Base pH Pump Relay On (Pump On)
|
||
MBMSK_PH_STATUS_CTRL_ACTIVE = 0x2000, // 13 Active pH control module and controlling pumps
|
||
MBMSK_PH_STATUS_MEASURE_ACTIVE = 0x4000, // 14 Active pH measurement module and making measurements. If this bit is at 1, the pH bar should be displayed.
|
||
MBMSK_PH_STATUS_MODULE_PRESENT = 0x8000, // 15 Detected pH measurement module
|
||
|
||
// MBF_RX_STATUS // bit
|
||
MBMSK_RX_STATUS_RX_PUMP_ACTIVE = 0x1000, // 12 Redox pump relay on (pump activated)
|
||
MBMSK_RX_STATUS_CTRL_ACTIVE = 0x2000, // 13 Active Redox control module and controlling pump
|
||
MBMSK_RX_STATUS_MEASURE_ACTIVE = 0x4000, // 14 Active Redox measurement module and performing measurements. If this bit is at 1, the Redox bar should be displayed on the screen.
|
||
MBMSK_RX_STATUS_MODULE_PRESENT = 0x8000, // 15 Redox measurement module detected in the system
|
||
|
||
// MBF_CL_STATUS // bit
|
||
MBMSK_CL_STATUS_CHLORINE_FLOW = 0x0008, // 3 Chlorine Probe Flow Sensor. This sensor is built into the probe itself and serves to detect whether there is water passing through the chlorine measurement probe. In case the sensor is at 0, the chlorine measurement will not be valid.
|
||
MBMSK_CL_STATUS_CL_PUMP_ACTIVE = 0x1000, // 12 Chlorine pump relay on (pump on)
|
||
MBMSK_CL_STATUS_CTRL_ACTIVE = 0x2000, // 13 Active chlorine control module and controlling pump
|
||
MBMSK_CL_STATUS_MEASURE_ACTIVE = 0x4000, // 14 Active chlorine measurement module and taking measurements. If this bit is 1, the chlorine bar should be displayed on the screen.
|
||
MBMSK_CL_STATUS_MODULE_PRESENT = 0x8000, // 15 Chlorine measurement module detected in the system
|
||
|
||
// MBF_CD_STATUS // bit
|
||
MBMSK_CD_STATUS_RX_PUMP_ACTIVE = 0x1000, // 12 Conductivity pump relay on (pump active)
|
||
MBMSK_CD_STATUS_CTRL_ACTIVE = 0x2000, // 13 Active conductivity control module and controlling pump
|
||
MBMSK_CD_STATUS_MEASURE_ACTIVE = 0x4000, // 14 Active conductivity measurement module and making measurements. If this bit is 1, the conditionality bar should be displayed on the screen.
|
||
MBMSK_CD_STATUS_MODULE_PRESENT = 0x8000, // 15 Conductivity measurement module detected in the system
|
||
|
||
// MBF_ION_STATUS // bit
|
||
MBMSK_ION_STATUS_ON_TARGET = 0x0001, // 0 On Target - the system has reached the set point.
|
||
MBMSK_ION_STATUS_LOW = 0x0002, // 1 Low - Ionization cannot reach the set point.
|
||
MBMSK_ION_STATUS_RESERVED = 0x0004, // 2 reserved
|
||
MBMSK_ION_STATUS_PROGTIME_EXCEEDED = 0x0008, // 3 Pr off - The programmed ionization time has been exceeded
|
||
MBMSK_ION_STATUS_POLOFF = 0x1000, // 12 Ion Pol off - Ionization in dead time
|
||
MBMSK_ION_STATUS_POL1 = 0x2000, // 13 Ion Pol 1 - Ionization working in polarization 1
|
||
MBMSK_ION_STATUS_POL2 = 0x4000, // 14 Ion Pol 2 - Ionization working in polarization 2
|
||
|
||
// MBF_HIDRO_STATUS // bit
|
||
MBMSK_HIDRO_STATUS_ON_TARGET = 0x0001, // 0 On Target - the system has reached the set point.
|
||
MBMSK_HIDRO_STATUS_LOW = 0x0002, // 1 Low - Hydrolysis cannot reach the set point.
|
||
MBMSK_HIDRO_STATUS_RESERVED = 0x0004, // 2 reserved
|
||
MBMSK_HIDRO_STATUS_FL1 = 0x0008, // 3 Flow - Hydrolysis cell flow indicator (FL1)
|
||
MBMSK_HIDRO_STATUS_COVER = 0x0010, // 4 Cover - Cover input activated
|
||
MBMSK_HIDRO_STATUS_MODULE_ACTIVE = 0x0020, // 5 Active - Active Module hydrolysis (hidroEnable)
|
||
MBMSK_HIDRO_STATUS_CTRL_ACTIVE = 0x0040, // 6 Control - Hydrolysis module working with regulation (hydroControlEnable)
|
||
MBMSK_HIDRO_STATUS_REDOX_ENABLED = 0x0080, // 7 Redox enable - Activation of hydrolysis by the redox module
|
||
MBMSK_HIDRO_STATUS_SHOCK_ENABLED = 0x0100, // 8 Hydro shock enabled - Chlorine shock mode enabled
|
||
MBMSK_HIDRO_STATUS_FL2 = 0x0200, // 9 FL2 - Chlorine probe flow indicator, if present
|
||
MBMSK_HIDRO_STATUS_ENABLED_BY_CHLORINE = 0x0400, // 10 Cl enable - Activation of hydrolysis by the chlorine module
|
||
MBMSK_HIDRO_STATUS_POLOFF = 0x1000, // 12 Ion Pol off - Ionization in dead time
|
||
MBMSK_HIDRO_STATUS_POL1 = 0x2000, // 13 Ion Pol 1 - Ionization working in polarization 1
|
||
MBMSK_HIDRO_STATUS_POL2 = 0x4000, // 14 Ion Pol 2 - Ionization working in polarization 2
|
||
|
||
// MBF_RELAY_STATE // bit
|
||
MBMSK_RELAY_STATE1 = 0x0001, // 0 Relay 1 state (1 on; 0 off) (normally assigned to ph)
|
||
MBMSK_RELAY_STATE2 = 0x0002, // 1 Relay 2 state (1 on; 0 off) (normally assigned to filtering)
|
||
MBMSK_RELAY_STATE3 = 0x0004, // 2 Relay 3 status (1 on; 0 off) (normally assigned to lighting)
|
||
MBMSK_RELAY_STATE4 = 0x0008, // 3 Relay 4 status (1 on; 0 off)
|
||
MBMSK_RELAY_STATE5 = 0x0010, // 4 Relay 5 status (1 on; 0 off)
|
||
MBMSK_RELAY_STATE6 = 0x0020, // 5 Relay 6 status (1 on; 0 off)
|
||
MBMSK_RELAY_STATE7 = 0x0040, // 6 Relay 7 status (1 on; 0 off)
|
||
MBMSK_RELAY_FILTSPEED_LOW = 0x0100, // 8 Filtration low speed
|
||
MBMSK_RELAY_FILTSPEED_MID = 0x0200, // 9 Filtration mid speed
|
||
MBMSK_RELAY_FILTSPEED_HIGH = 0x0400, // 10 Filtration high speed
|
||
MBMSK_RELAY_FILTSPEED = 0x0700, // Filtration current speed mask
|
||
MBSHFT_RELAY_FILTSPEED = 8, // Filtration current speed bit shift
|
||
|
||
// MBF_NOTIFICATION // bit
|
||
MBMSK_NOTIF_MODBUS_CHANGED = 0x0001, // 0 Modbus page changed
|
||
MBMSK_NOTIF_GLOBAL_CHANGED = 0x0002, // 1 Global page changed
|
||
MBMSK_NOTIF_FACTORY_CHANGED = 0x0004, // 2 Factory page changed
|
||
MBMSK_NOTIF_INSTALLER_CHANGED = 0x0008, // 3 Installer page changed
|
||
MBMSK_NOTIF_USER_CHANGED = 0x0010, // 4 User page changed
|
||
MBMSK_NOTIF_MISC_CHANGED = 0x0020, // 5 Misc page changed
|
||
|
||
// MBF_CELL_BOOST
|
||
MBMSK_CELL_BOOST_NO_REDOX_CTL = 0x8000, // ! Disable redox ctrl
|
||
MBMSK_CELL_BOOST_STATE = 0x0500, // ! Boost
|
||
MBMSK_CELL_BOOST_START = 0x00A0, // ! Start boost
|
||
|
||
// MBF_PAR_MODEL // bit
|
||
MBMSK_MODEL_ION = 0x0001, // 0 The equipment includes ionization control
|
||
MBMSK_MODEL_HIDRO = 0x0002, // 1 The equipment includes hydrolysis or electrolysis
|
||
MBMSK_MODEL_UV = 0x0004, // 2 The equipment includes disinfection control by ultraviolet lamp
|
||
MBMSK_MODEL_SALINITY = 0x0008, // 3 The equipment includes measurement of salinity (Fanless equipment only)
|
||
|
||
// MBF_PAR_HIDRO_FLOW_SIGNAL
|
||
MBV_PAR_HIDRO_FLOW_SIGNAL_STD = 0, // Standard detection based on conduction between an auxiliary electrode and either of the two electrodes of the cell.
|
||
MBV_PAR_HIDRO_FLOW_SIGNAL_ALWAYS_ON = 1, // Always connected. This value allows forcing the generation of the hydrolysis current even if no flow is detected in the sensor.
|
||
MBV_PAR_HIDRO_FLOW_SIGNAL_PADDLE = 2, // Detection based on the paddle switch, associated with the FL1 input
|
||
MBV_PAR_HIDRO_FLOW_SIGNAL_PADDLE_AND_STD= 3, // Detection based on the paddle switch, associated with the FL1 input, and the standard detector. The system will understand that there is flow when both elements detect flow. If either one opens, the hydrolysis will stop.
|
||
MBV_PAR_HIDRO_FLOW_SIGNAL_PADDLE_OR_STD = 4, // Detection based on the paddle switch, associated with the FL1 input, or the standard detector. The system will understand that there is flow when either of the two elements detects flow. Hydrolysis will stop only if both detectors detect no flow.
|
||
|
||
// MBF_PAR_HIDRO_ION_CAUDAL // bit
|
||
MBMSK_HIDRO_ION_CAUDAL_FL1_CTRL = 0x0001, // 0 If the FL1 signal is detected to be inactive, the actuation of the different elements of the system is disabled.
|
||
MBMSK_HIDRO_ION_CAUDAL_FL2_CTRL = 0x0002, // 1 If the FL2 signal is detected to be inactive, the actuation of the different elements of the system is disabled.
|
||
MBMSK_HIDRO_ION_CAUDAL_FULL_CL_HIDRO_CTRL=0x0004, // 2 If there is a chlorine module installed and it is detected that its flow sensor is inactive, the action of the different elements of the system is disabled.
|
||
MBMSK_HIDRO_ION_CAUDAL_SLAVE = 0x0008, // 3 The value of the slave input is taken and if it is inactive, the action of the different elements of the system is disabled.
|
||
MBMSK_HIDRO_ION_CAUDAL_PADDLE_SWITCH = 0x0010, // 4
|
||
MBMSK_HIDRO_ION_CAUDAL_PADDLE_SWITCH_INV= 0x0020, // 5
|
||
MBMSK_HIDRO_ION_CAUDAL_INVERSION = 0x0080, // 7 This bit determines if active means open or closed for the input electrical signals, and allows to reverse the operation for example to implement a paddle switch that closes when there is no flow.
|
||
|
||
// MBF_PAR_FILT_MODE
|
||
MBV_PAR_FILT_MANUAL = 0, // This mode allows to turn the filtration (and all other systems that depend on it) on and off manually.
|
||
MBV_PAR_FILT_AUTO = 1, // This mode allows filtering to be turned on and off according to the settings of the TIMER1, TIMER2 and TIMER3 timers.
|
||
MBV_PAR_FILT_HEATING = 2, // This mode is similar to the AUTO mode, but includes setting the temperature for the heating function. This mode is activated only if the MBF_PAR_HEATING_MODE register is at 1 and there is a heating relay assigned.
|
||
MBV_PAR_FILT_SMART = 3, // This filtration mode adjusts the pump operating times depending on the temperature. This mode is activated only if the MBF_PAR_TEMPERATURE_ACTIVE register is at 1.
|
||
MBV_PAR_FILT_INTELLIGENT = 4, // This mode performs an intelligent filtration process in combination with the heating function. This mode is activated only if the MBF_PAR_HEATING_MODE register is at 1 and there is a heating relay assigned.
|
||
MBV_PAR_FILT_BACKWASH = 13, // This filter mode is started when the backwash operation is activated.
|
||
|
||
// MBF_PAR_UV_MODE
|
||
MBV_PAR_UV_MODE0 = 0, // UV is switched off and it will not turn on when filtration starts
|
||
MBV_PAR_UV_MODE1 = 1, // UV is switched on and it will turn on when filtration starts. Time counter for the UV lamp will be incremented.
|
||
|
||
// MBF_PAR_UV_HIDE_WARN // bit
|
||
MBMSK_UV_HIDE_WARN_CLEAN = 0x0001, // 0 Suppress UV lamp clean wanring
|
||
MBMSK_UV_HIDE_WARN_REPLACE = 0x0002, // 1 Suppress UV lamp replace warning
|
||
|
||
// MBF_PAR_PH_PUMP_REP_TIME_ON
|
||
MBMSK_PH_PUMP_TIME = 0x7FFF, // Time level for the pump: The time level has a special coding format. It can cover periods of 1 to 180 seconds with 1 second granularity and from 3 to 999 minutes with 1 minute granularity. f the value is set to 30 for example, a 30 second time will be considered. If we have the value 200, we will have an on time of (200-180+3) = 23 minutes.
|
||
MBMSK_PH_PUMP_REPETITIVE = 0x8000, // pH pump in repetitive mode (1)
|
||
|
||
// MBF_PAR_HIDRO_COVER_ENABLE
|
||
MBMSK_HIDRO_COVER_ENABLE = 0x0001, // If enabled, the hydrolysis/electrolysis production will be reduced by a given percentage specified in the lower half of the MBF_PAR_HIDRO_COVER_REDUCTION register when the cover input is detected.
|
||
MBMSK_HIDRO_TEMPERATURE_SHUTDOWN_ENABLE = 0x0002, // If enabled, the hydrolysis/electrolysis production will stop when the temperature falls below a given temperature threshold specified in higher part of the MBF_PAR_HIDRO_COVER_REDUCTION register.
|
||
// MBF_PAR_HIDRO_COVER_REDUCTION
|
||
MBMSK_HIDRO_COVER_REDUCTION = 0x00FF, // Percentage for the cover reduction
|
||
MBMSK_HIDRO_SHUTDOWN_TEMPERATURE = 0xFF00, // Temperature level for the hydrolysis shutdown
|
||
|
||
// MBF_PAR_RELAY_PH
|
||
MBV_PAR_RELAY_PH_ACID_AND_BASE = 0, // The equipment works with an acid and base pump
|
||
MBV_PAR_RELAY_PH_ACID_ONLY = 1, // The equipment works with acid pump only
|
||
MBV_PAR_RELAY_PH_BASE_ONLY = 2, // The equipment works with base pump only
|
||
|
||
//MBF_PAR_RELAY_MODE
|
||
MBMSK_PAR_RELAY_MODE_PH = 0x0003, // Behavior for the pH module (MBV_PAR_RELAY_MODE_*)
|
||
MBMSK_PAR_RELAY_MODE_RX = 0x000C, // Behavior for the Redox module (MBV_PAR_RELAY_MODE_*)
|
||
MBMSK_PAR_RELAY_MODE_CL = 0x0030, // Behavior for the Chlorine module (MBV_PAR_RELAY_MODE_*)
|
||
MBMSK_PAR_RELAY_MODE_CD = 0x00C0, // Behavior for the Conductivity module (MBV_PAR_RELAY_MODE_*)
|
||
MBMSK_PAR_RELAY_MODE_TURBIDITY = 0x0300, // Behavior for the Turbidity module (MBV_PAR_RELAY_MODE_*)
|
||
MBV_PAR_RELAY_MODE_IGNORE = 0, // The system simply ignores the alarm and dosing continues.
|
||
MBV_PAR_RELAY_MODE_SHOW_ONLY = 1, // The system only shows the alarm on screen, but the dosing continues.
|
||
MBV_PAR_RELAY_MODE_SHOW_AND_STOP = 2, // The system shows the alarm on screen and stops the dosing pump
|
||
|
||
// MBF_PAR_FILTRATION_CONF
|
||
MBMSK_PAR_FILTRATION_CONF_TYPE = 0x000F, // Filtration pump type, see MBV_PAR_FILTRATION_TYPE_*
|
||
MBMSK_PAR_FILTRATION_CONF_DEF_SPEED = 0x0070, // Filtration default speed, see MBV_PAR_FILTRATION_SPEED_*
|
||
MBMSK_PAR_FILTRATION_CONF_INT1_SPEED = 0x0380, // Filtration speed for timer interval 1, see MBV_PAR_FILTRATION_SPEED_*
|
||
MBMSK_PAR_FILTRATION_CONF_INT2_SPEED = 0x1C00, // Filtration speed for timer interval 2, see MBV_PAR_FILTRATION_SPEED_*
|
||
MBMSK_PAR_FILTRATION_CONF_INT3_SPEED = 0xE000, // Filtration speed for timer interval 3, see MBV_PAR_FILTRATION_SPEED_*
|
||
MBSHFT_PAR_FILTRATION_CONF_TYPE = 0, // Filtration pump type bit shift
|
||
MBSHFT_PAR_FILTRATION_CONF_DEF_SPEED = 4, // Filtration default speed bit shift
|
||
MBSHFT_PAR_FILTRATION_CONF_INT1_SPEED = 7, // Filtration speed for timer interval 1 bit shift
|
||
MBSHFT_PAR_FILTRATION_CONF_INT2_SPEED = 10, // Filtration speed for timer interval 2 bit shift
|
||
MBSHFT_PAR_FILTRATION_CONF_INT3_SPEED = 13, // Filtration speed for timer interval 3 bit shift
|
||
MBV_PAR_FILTRATION_TYPE_STANDARD = 0, // Standard (without speed control)
|
||
MBV_PAR_FILTRATION_TYPE_HAYWARD = 1, // Variable speed B
|
||
MBV_PAR_FILTRATION_TYPE_SPEED_B = 2, // Variable speed B
|
||
MBV_PAR_FILTRATION_SPEED_SLOW = 0, // Speed Slow
|
||
MBV_PAR_FILTRATION_SPEED_MEDIUM = 1, // Speed Medium
|
||
MBV_PAR_FILTRATION_SPEED_FAST = 2, // Speed Fast
|
||
|
||
// MBF_PAR_FUNCTION_DEPENDENCY
|
||
MBMSK_FCTDEP_HEATING = 0x0007, // Heating function dependency:
|
||
MBMSK_DEPENDENCY_FL1_PADDLE = 0x0001,
|
||
MBMSK_DEPENDENCY_FL2 = 0x0002,
|
||
MBMSK_DEPENDENCY_SLAVE = 0x0004,
|
||
|
||
// MBF_PAR_UICFG_MACHINE
|
||
MBV_PAR_MACH_NONE = 0, // No machine assigned
|
||
MBV_PAR_MACH_HIDROLIFE = 1, // Hidrolife (yellow)
|
||
MBV_PAR_MACH_AQUASCENIC = 2, // Aquascenic (blue)
|
||
MBV_PAR_MACH_OXILIFE = 3, // Oxilife (green)
|
||
MBV_PAR_MACH_BIONET = 4, // Bionet (light blue)
|
||
MBV_PAR_MACH_HIDRONISER = 5, // Hidroniser (red)
|
||
MBV_PAR_MACH_UVSCENIC = 6, // UVScenic (lilac)
|
||
MBV_PAR_MACH_STATION = 7, // Station (orange)
|
||
MBV_PAR_MACH_BRILIX = 8, // Brilix
|
||
MBV_PAR_MACH_GENERIC = 9, // Generic
|
||
MBV_PAR_MACH_BAYROL = 10, // Bayrol
|
||
MBV_PAR_MACH_HAY = 11, // Hay
|
||
|
||
// MBF_PAR_UICFG_LANGUAGE
|
||
MBV_PAR_LANG_SPANISH = 0,
|
||
MBV_PAR_LANG_ENGLISH = 1,
|
||
MBV_PAR_LANG_FRENCH = 2,
|
||
MBV_PAR_LANG_GERMAN = 3,
|
||
MBV_PAR_LANG_ITALIAN = 4,
|
||
MBV_PAR_LANG_TURKISH = 5,
|
||
MBV_PAR_LANG_CZECH = 6,
|
||
MBV_PAR_LANG_PORTUGUESE = 7,
|
||
MBV_PAR_LANG_DUTCH = 8,
|
||
MBV_PAR_LANG_POLISH = 9,
|
||
MBV_PAR_LANG_HUNGARIAN = 10,
|
||
MBV_PAR_LANG_RUSSIAN = 11,
|
||
|
||
// MBF_PAR_UICFG_BACKLIGHT
|
||
MBV_PAR_BACKLIGHT_15SEC = 0, // Backlight off after 15 sec
|
||
MBV_PAR_BACKLIGHT_30SEC = 1, // Backlight off after 30 sec
|
||
MBV_PAR_BACKLIGHT_60SEC = 2, // Backlight off after 60 sec
|
||
MBV_PAR_BACKLIGHT_5MIN = 3, // Backlight off after 5 min
|
||
MBV_PAR_BACKLIGHT_ON = 4, // Backlight never turns off
|
||
|
||
// MBF_PAR_TIMER_BLOCK_BASE
|
||
MBV_TIMER_OFFMB_TIMER_ENABLE = 0, // Enables the timer function in the selected working mode, see MBF_PAR_CTIMER_*
|
||
MBV_TIMER_OFFMB_TIMER_ON = 1, // Timer start (32-bit timestamp, LSB first)
|
||
MBV_TIMER_OFFMB_TIMER_OFF = 3, // Timer stop (32-bit timestamp, LSB first) - not used
|
||
MBV_TIMER_OFFMB_TIMER_PERIOD = 5, // Time in seconds between starting points (32-bit, LSB first), e.g. 86400 means daily
|
||
MBV_TIMER_OFFMB_TIMER_INTERVAL = 7, // Time in seconds that the timer has to run when started (32-bit, LSB first)
|
||
MBV_TIMER_OFFMB_TIMER_COUNTDOWN = 9, // Time remaining in seconds for the countdown mode (32-bit, LSB first)
|
||
MBV_TIMER_OFFMB_TIMER_FUNCTION = 11, // Function assigned to this timer, see MBV_PAR_CTIMER_FCT_*
|
||
MBV_TIMER_OFFMB_TIMER_WORK_TIME = 13, // Number of seconds that the timer has been operating (valid only if MBV_TIMER_OFFMB_TIMER_ENABLE is in MBV_PAR_CTIMER_COUNTDOWN_KEY_* mode)
|
||
// MBV_TIMER_OFFMB_TIMER_ENABLE working modes:
|
||
MBV_PAR_CTIMER_DISABLE = 0, // Timer disabled
|
||
MBV_PAR_CTIMER_ENABLED = 1, // Timer enabled and independent
|
||
MBV_PAR_CTIMER_ENABLED_LINKED = 2, // Timer enabled and linked to relay from timer 0
|
||
MBV_PAR_CTIMER_ALWAYS_ON = 3, // Relay assigned to this timer always on
|
||
MBV_PAR_CTIMER_ALWAYS_OFF = 4, // Relay assigned to this timer always off
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_PLUS = 0x0105, // Timer in countdown mode using + key
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_MINUS = 0x0205, // Timer in countdown mode using - key
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_ARROWDOWN = 0x0405, // Timer in countdown mode using arrow-down key
|
||
|
||
// MBF_SET_COUNTDOWN_KEY_AUX_* mask:
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX1 = 0x0100, // Aux1 ON/OFF, when MBV_TIMER_OFFMB_TIMER_ENABLE is set to MBV_PAR_CTIMER_COUNTDOWN_KEY_*
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX2 = 0x0200, // Aux2 ON/OFF, when MBV_TIMER_OFFMB_TIMER_ENABLE is set to MBV_PAR_CTIMER_COUNTDOWN_KEY_*
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX3 = 0x0400, // Aux3 ON/OFF, when MBV_TIMER_OFFMB_TIMER_ENABLE is set to MBV_PAR_CTIMER_COUNTDOWN_KEY_*
|
||
MBV_PAR_CTIMER_COUNTDOWN_KEY_AUX4 = 0x0800, // Aux4 ON/OFF, when MBV_TIMER_OFFMB_TIMER_ENABLE is set to MBV_PAR_CTIMER_COUNTDOWN_KEY_*
|
||
|
||
// MBV_TIMER_OFFMB_TIMER_FUNCTION codes:
|
||
MBV_PAR_CTIMER_FCT_FILTRATION = 0x0001, // Filtration function of the system
|
||
MBV_PAR_CTIMER_FCT_LIGHTING = 0x0002, // Lighting function of the system
|
||
MBV_PAR_CTIMER_FCT_HEATING = 0x0004, // Heating function of the system
|
||
MBV_PAR_CTIMER_FCT_RELAY1 = 0x0100, // Function assigned to relay 1 (Filtration)
|
||
MBV_PAR_CTIMER_FCT_RELAY2 = 0x0200, // Function assigned to relay 2 (Light)
|
||
MBV_PAR_CTIMER_FCT_RELAY3 = 0x0400, // Function assigned to relay 3 (pH Pump)
|
||
MBV_PAR_CTIMER_FCT_RELAY4 = 0x0800, // Function assigned to relay 4 (AUX1)
|
||
MBV_PAR_CTIMER_FCT_RELAY5 = 0x1000, // Function assigned to relay 5 (AUX2)
|
||
MBV_PAR_CTIMER_FCT_RELAY6 = 0x2000, // Function assigned to relay 6 (AUX3)
|
||
MBV_PAR_CTIMER_FCT_RELAY7 = 0x4000, // Function assigned to relay 7 (AUX4)
|
||
|
||
// MBF_PAR_UICFG_SOUND // bit
|
||
MBMSK_PAR_SOUND_CLICK = 0x0001, // 0 Click sounds every time a key is pressed
|
||
MBMSK_PAR_SOUND_POPUP = 0x0002, // 1 Sound plays each time a pop-up message appears
|
||
MBMSK_PAR_SOUND_ALERTS = 0x0004, // 2 An alarm sounds when there is an alert on the equipment (AL3)
|
||
MBMSK_PAR_SOUND_FILTRATION = 0x0008, // 3 Audible warning every time the filtration is started
|
||
|
||
// MBF_PAR_UICFG_VISUAL_OPTIONS // bit
|
||
MBMSK_HIDE_TEMPERATURE = 0x0001, // 0 Hide temperature measurement from main menu
|
||
MBMSK_HIDE_FILTRATION = 0x0002, // 1 Hide filter status from main menu
|
||
MBMSK_HIDE_LIGHTING = 0x0004, // 2 Hide lighting status from main menu
|
||
MBMSK_HIDE_AUX_RELAYS = 0x0008, // 3 Hide auxiliary relay status from main menu.
|
||
MBMSK_VO_HIDE_EXTRA_REGS = 0x0010, // 4 Hide the option to adjust additional registers in the installer menu
|
||
MBMSK_VO_HIDE_RELAY_CONFIG = 0x0020, // 5 Hide the relay configuration option in the installer menu.
|
||
MBMSK_VO_SLOW_FILTER_HIDRO_LEVEL = 0x0040, // 6 This option enables the slow hydrolysis level filtering option when the pH module is installed. This is especially important when the acid / base dosing is done very close to the hydrolysis probe.
|
||
MBMSK_VO_HIDE_SALINITY_MAIN_WINDOW = 0x0080, // 7 Hides the salinity measurement from main screen.
|
||
MBMSK_VO_SHOW_SPECIAL_REGS = 0x0100, // 8 Displays the special register set configuration menu in the installer menu.
|
||
MBMSK_SHOW_HID_SHUTDOWN_BY_TEMPERATURE = 0x0200, // 9 Displays the option to turn off hydrolysis by temperature.
|
||
MBMSK_SHOW_CELL_SELECTION = 0x0400, // 10 Enables access to the cell selection menu from the service menu option of the configuration menu.
|
||
MBMSK_SHOW_PUMP_TYPE = 0x0800, // 11 Displays the option for selecting the type of filtration pump (normal, three speeds, etc.).
|
||
MBMSK_SHOW_QUICK_MENU = 0x1000, // 12 Displays the quick access menu instead of the conventional menu, when the SET key is pressed from the main display screen. Filtration (normal, three speeds, etc).
|
||
MBMSK_SHOW_OXI_MAIN_DATA_SCREEN = 0x2000, // 13 Displays main screen shown with a particular style called OXI
|
||
MBMSK_SHOW_INSTALLER_MENU = 0x4000, // 14 Shows access to the installer menu in the main menu without the need for a password.
|
||
MBMSK_SHOW_FACTORY_MENU = 0x8000, // 15 Shows access to the factory menu in the main menu without the need for a password.
|
||
|
||
// MBF_PAR_UICFG_VISUAL_OPTIONS_EXT // bit
|
||
MBMSK_VOE_SHOW_PNEUMATIC_VALVE = 0x0001, // 0 Shows the pneumatic valve
|
||
MBMSK_VOE_HIDE_AUX_REL_DEPENDENCY = 0x0002, // 1 Hides the auxiliary relay dependency
|
||
MBMSK_VOE_SHOW_BESGO_NAME = 0x0004, // 2 Show “Besgo” instead of “Pneumatic” for the pneumatic valve titles.
|
||
MBMSK_VOE_HIDE_SMART_INTEL_MODES = 0x0008, // 3 Hide smart intelligent modes
|
||
MBMSK_VOE_ENABLE_ADVANCED_CALIBRATION = 0x0010, // 4 Enable advanced calibration
|
||
MBMSK_VOE_SHOW_DOSING_PUMP_SCALING = 0x0020, // 5 Show dosing pump scaling
|
||
MBMSK_VOE_HIDE_MASTER_SLAVE_SEL = 0x0030, // 6 Hide master slave selection
|
||
MBMSK_VOE_ENABLE_ADVANCED_MASTER_SLAVE = 0x0080, // 7 Enable advanced Master Slave
|
||
MBMSK_VOE_SHOW_OVERTEMP_PROTECTION = 0x0100, // 8 Show overtemperature protection
|
||
MBMSK_VOE_SHOW_HYDRO_MODE_SEL = 0x0200, // 9 Show hidrolysis mode selection
|
||
MBMSK_VOE_SHOW_ADVANCED_EXT_CONTROL = 0x0400, // 10 Show advanced external controls
|
||
MBMSK_VOE_HIDE_BASIC_EXT_CONTROL = 0x0800, // 11 Hide basic external controls
|
||
MBMSK_VOE_HIDE_DIAGNOSTICS = 0x1000, // 12 Hide diagnostics
|
||
|
||
// MBF_PAR_UICFG_MACH_VISUAL_STYLE // bit
|
||
MBMSK_VS_FORCE_UNITS_GRH = 0x2000, // 13 Display the hydrolysis/electrolysis in units of grams per hour (gr/h).
|
||
MBMSK_VS_FORCE_UNITS_PERCENTAGE = 0x4000, // 14 Display the hydrolysis/electrolysis in percentage units (%).
|
||
MBMSK_ELECTROLISIS = 0x8000, // 15 Display the word electrolysis instead of hydrolysis in generic mode.
|
||
// To determine the type of units are used to display the hydrolysis/electrolysis:
|
||
// 1. If MBMSK_VS_FORCE_UNITS_PERCENTAGE bit is set, "%" is displayed
|
||
// 2. If MBMSK_VS_FORCE_UNITS_GRH bit is set, "gr/h" is displayed
|
||
// 3. If neither of the above two bits is set:
|
||
// a. If MBF_PAR_UICFG_MACHINE is MBV_PAR_MACH_HIDROLIFE or MBV_PAR_MACH_BIONET, then "gr/h" is displayed
|
||
// b. If MBF_PAR_UICFG_MACHINE is MBV_PAR_MACH_GENERIC and MBMSK_ELECTROLISIS bit is set, "gr/h" is displayed.
|
||
// c. If none of the above cases apply, "%" is displayed.
|
||
|
||
// MBF_POWER_MODULE_REG_*
|
||
MBV_POWER_MODULE_REG_INFO = 0, // ! set of 26-byte power module register stores an ASCIIZ string containing the subversion and timestamp of the module, e. g. ".57\nMay 26 2020\n01:08:10\n\0"
|
||
};
|
||
|
||
#include <TasmotaModbus.h>
|
||
TasmotaModbus *NeoPoolModbus;
|
||
|
||
// emulates a g/h system !!!! only for development purposes on % systems
|
||
// enables also cmnd 'NPgPerh [0|1]': 0 = disables emulation, 1 enables emulation
|
||
//#define NEOPOOL_EMULATE_GPERH 16 // Max g/h power of an emulated % system
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
bool neopool_system_gperh = false; // emulation defaults off
|
||
#endif
|
||
|
||
|
||
#define NEOPOOL_RELAY_MAX 7 // Number of relais build-in
|
||
|
||
bool neopool_active = false;
|
||
volatile bool neopool_poll = true;
|
||
|
||
uint8_t neopool_read_state = 0;
|
||
uint8_t neopool_send_retry = 0;
|
||
uint8_t neopool_failed_count = 0;
|
||
bool neopool_error = true;
|
||
|
||
uint16_t neopool_power_module_version;
|
||
uint16_t neopool_power_module_nodeid[6];
|
||
|
||
#define NEOPOOL_MAX_REPEAT_ON_ERROR 10
|
||
uint8_t neopool_repeat_on_error = 2;
|
||
|
||
uint16_t neopool_light_relay;
|
||
uint8_t neopool_light_prg_delay;
|
||
uint8_t neopoll_cmd_delay = 0;
|
||
void (* neopoll_cmd)(void) = nullptr;
|
||
|
||
// Modbus register set to read
|
||
// Defines blocks of register read once with a single read
|
||
typedef struct {
|
||
const uint16_t addr;
|
||
const uint16_t cnt;
|
||
uint16_t *data;
|
||
} TNeoPoolReg;
|
||
|
||
// complete poll cycle needs 2000 ms to read complete register set
|
||
#define NEOPOOL_REG_QUERY {\
|
||
{MBF_ION_CURRENT, MBF_NOTIFICATION - MBF_ION_CURRENT + 1, nullptr},\
|
||
{MBF_CELL_RUNTIME_LOW, MBF_CELL_RUNTIME_POL_CHANGES_HIGH - MBF_CELL_RUNTIME_LOW + 1, nullptr},\
|
||
{MBF_PAR_VERSION, MBF_PAR_HIDRO_NOM - MBF_PAR_VERSION + 1, nullptr},\
|
||
{MBF_PAR_TIME_LOW, MBF_PAR_HEATING_GPIO - MBF_PAR_TIME_LOW + 1, nullptr},\
|
||
{MBF_PAR_ION, MBF_PAR_FILTRATION_CONF - MBF_PAR_ION + 1, nullptr},\
|
||
{MBF_PAR_UICFG_MACHINE, MBF_PAR_UICFG_MACH_VISUAL_STYLE - MBF_PAR_UICFG_MACHINE + 1, nullptr},\
|
||
{MBF_VOLT_24_36, MBF_VOLT_12 - MBF_VOLT_24_36 + 1, nullptr},\
|
||
{MBF_VOLT_5, MBF_AMP_4_20_MICRO - MBF_VOLT_5 + 1, nullptr}\
|
||
}
|
||
TNeoPoolReg NeoPoolReg[] = NEOPOOL_REG_QUERY;
|
||
|
||
// Register to check for NPTelePeriod changes
|
||
const uint16_t NeoPoolRegCheck[] PROGMEM = {
|
||
// excl. values that change almost continuously
|
||
// MBF_PAR_TIME_LOW,
|
||
// MBF_PAR_TIME_HIGH,
|
||
// MBF_VOLT_12,
|
||
// MBF_VOLT_24_36,
|
||
// MBF_VOLT_5,
|
||
// MBF_AMP_4_20_MICRO,
|
||
// MBF_CELL_RUNTIME_LOW,
|
||
// MBF_CELL_RUNTIME_HIGH,
|
||
// MBF_CELL_RUNTIME_PART_LOW,
|
||
// MBF_CELL_RUNTIME_PART_HIGH,
|
||
// MBF_CELL_RUNTIME_POLA_LOW,
|
||
// MBF_CELL_RUNTIME_POLA_HIGH,
|
||
// MBF_CELL_RUNTIME_POLB_LOW,
|
||
// MBF_CELL_RUNTIME_POLB_HIGH,
|
||
// MBF_CELL_RUNTIME_POL_CHANGES_LOW,
|
||
// MBF_CELL_RUNTIME_POL_CHANGES_HIGH,
|
||
|
||
// measured values delayed (set bit 15 to indicate often value changes)
|
||
MBF_MEASURE_TEMPERATURE | 0x8000,
|
||
|
||
// undelayed measured values
|
||
MBF_MEASURE_CL,
|
||
MBF_MEASURE_CONDUCTIVITY,
|
||
MBF_MEASURE_PH,
|
||
MBF_MEASURE_RX,
|
||
MBF_ION_CURRENT,
|
||
MBF_HIDRO_CURRENT,
|
||
MBF_HIDRO_STATUS,
|
||
MBF_PH_STATUS,
|
||
MBF_RELAY_STATE,
|
||
|
||
// undelayed setting values
|
||
MBF_CELL_BOOST,
|
||
MBF_PAR_CL1,
|
||
MBF_PAR_FILT_MODE,
|
||
MBF_PAR_HIDRO,
|
||
MBF_PAR_HIDRO_NOM,
|
||
MBF_PAR_ION,
|
||
MBF_PAR_PH1,
|
||
MBF_PAR_PH2,
|
||
MBF_PAR_RX1,
|
||
|
||
MBF_PAR_CD_RELAY_GPIO,
|
||
MBF_PAR_CL_RELAY_GPIO,
|
||
MBF_PAR_FILT_GPIO,
|
||
MBF_PAR_FILTVALVE_GPIO,
|
||
MBF_PAR_HEATING_GPIO,
|
||
MBF_PAR_LIGHTING_GPIO,
|
||
MBF_PAR_PH_ACID_RELAY_GPIO,
|
||
MBF_PAR_PH_BASE_RELAY_GPIO,
|
||
MBF_PAR_RX_RELAY_GPIO,
|
||
MBF_PAR_UV_RELAY_GPIO,
|
||
MBF_PAR_ION_NOM,
|
||
MBF_PAR_TEMPERATURE_ACTIVE,
|
||
MBF_PAR_UICFG_MACHINE
|
||
};
|
||
uint16_t *NeoPoolRegCheckData;
|
||
uint32_t NeoPoolRegCheckDataTimeout = 0;
|
||
|
||
|
||
typedef struct {
|
||
uint16_t addr;
|
||
uint16_t data;
|
||
uint32_t ts;
|
||
} TNeoPoolData;
|
||
uint16_t NeoPoolDataCount = 0;
|
||
TNeoPoolData* NeoPoolData = nullptr;
|
||
|
||
// NeoPool modbus function errors
|
||
enum NeoPoolModbusCode {
|
||
NEOPOOL_MODBUS_OK = 0,
|
||
NEOPOOL_MODBUS_ERROR_RW_DATA,
|
||
NEOPOOL_MODBUS_ERROR_TIMEOUT,
|
||
NEOPOOL_MODBUS_ERROR_OUT_OF_MEM,
|
||
NEOPOOL_MODBUS_ERROR_DEADLOCK
|
||
};
|
||
|
||
#ifdef NEOPOOL_RANGE_CHECKS
|
||
#define NEOPOOL_UNDEF_UINT16 0xFFFF
|
||
typedef struct {
|
||
uint16_t addr; // Modbus register addr
|
||
uint16_t min; // min valid value (or UNDEFined)
|
||
uint16_t max; // max valid value (or UNDEFined)
|
||
uint16_t prev; // previous read value
|
||
} TNeoPoolRangeCheck;
|
||
TNeoPoolRangeCheck NeoPoolRangeCheck[] = {
|
||
{MBF_ION_CURRENT, 0, 100, NEOPOOL_UNDEF_UINT16}, // Ionization level measured
|
||
{MBF_HIDRO_CURRENT, 0, NEOPOOL_UNDEF_UINT16, NEOPOOL_UNDEF_UINT16}, // Hydrolysis intensity level
|
||
{MBF_MEASURE_PH, 0, 1400, NEOPOOL_UNDEF_UINT16}, // pH level measured
|
||
{MBF_MEASURE_RX, 0, 1000, NEOPOOL_UNDEF_UINT16}, // Redox level measured
|
||
{MBF_MEASURE_CL, 0, 1000, NEOPOOL_UNDEF_UINT16}, // Chlorine level measured
|
||
{MBF_MEASURE_CONDUCTIVITY, 0, 100, NEOPOOL_UNDEF_UINT16}, // Conductivity level measured
|
||
{MBF_MEASURE_TEMPERATURE, 0, 6500, NEOPOOL_UNDEF_UINT16} // Temperature sensor measured
|
||
};
|
||
#endif
|
||
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
#define NEOPOOL_TASMOTAMODBUS_ERROR_NUM_MAX 15 // 0-14 - see TasmotaModbus.h class TasmotaModbus highest error #
|
||
// counting modbus and data error
|
||
struct {
|
||
uint32_t time; // time where counting started
|
||
uint32_t mb_requests; // request count
|
||
// result count:
|
||
uint32_t mb_results[NEOPOOL_TASMOTAMODBUS_ERROR_NUM_MAX + 1];
|
||
uint32_t value_out_of_range; // value out of range count
|
||
} NeoPoolStats;
|
||
#endif
|
||
|
||
// NPResult possible values
|
||
enum NeoPoolResult {
|
||
NEOPOOL_RESULT_DEC = false,
|
||
NEOPOOL_RESULT_HEX,
|
||
NEOPOOL_RESULT_MAX
|
||
};
|
||
|
||
// Sensor saved variables
|
||
#define NEOPOOL_SETTING_VERSION 0x0100
|
||
#define NEOPOOL_DEFAULT_PHRES 1
|
||
#define NEOPOOL_DEFAULT_CLRES 1
|
||
#define NEOPOOL_DEFAULT_IONRES 1
|
||
#define NEOPOOL_DEFAULT_RESULT NEOPOOL_RESULT_HEX
|
||
#define NEOPOOL_DEFAULT_NPTELEPERIOD 0
|
||
|
||
typedef union {
|
||
uint16_t data; // Allow bit manipulation
|
||
struct {
|
||
uint16_t ph : 2; // bit 0,1 - pH value resolution
|
||
uint16_t cl : 2; // bit 2,3 - CL value resolution
|
||
uint16_t ion : 2; // bit 4,5 - ION value resolution
|
||
uint16_t range_check : 1; // bit 6 - enable data validation and repair
|
||
uint16_t conn_stat : 1; // bit 7 - enable connection statistic
|
||
uint16_t spare08 : 1; // bit 8
|
||
uint16_t spare09 : 1; // bit 9
|
||
uint16_t spare10 : 1; // bit 10
|
||
uint16_t spare11 : 1; // bit 11
|
||
uint16_t spare12 : 1; // bit 12
|
||
uint16_t spare13 : 1; // bit 13
|
||
uint16_t spare14 : 1; // bit 14
|
||
uint16_t spare15 : 1; // bit 15
|
||
};
|
||
} NeoPoolBitfield;;
|
||
|
||
// Global structure containing sensor saved variables
|
||
struct {
|
||
uint32_t crc32;
|
||
uint16_t version;
|
||
NeoPoolBitfield flags;
|
||
uint8_t result;
|
||
uint16_t npteleperiod;
|
||
} NeoPoolSettings;
|
||
|
||
|
||
#define D_NEOPOOL_NAME "NeoPool"
|
||
|
||
#define D_NEOPOOL_JSON_FILTRATION_NONE ""
|
||
#define D_NEOPOOL_JSON_FILTRATION_MANUAL "Manual"
|
||
#define D_NEOPOOL_JSON_FILTRATION_AUTO "Auto"
|
||
#define D_NEOPOOL_JSON_FILTRATION_HEATING "Heating"
|
||
#define D_NEOPOOL_JSON_FILTRATION_SMART "Smart"
|
||
#define D_NEOPOOL_JSON_FILTRATION_INTELLIGENT "Intelligent"
|
||
#define D_NEOPOOL_JSON_FILTRATION_BACKWASH "Backwash"
|
||
#define D_NEOPOOL_JSON_MODULES "Modules"
|
||
#define D_NEOPOOL_JSON_POWERUNIT "Powerunit"
|
||
#define D_NEOPOOL_JSON_CHLORINE "Chlorine"
|
||
#define D_NEOPOOL_JSON_CONDUCTIVITY "Conductivity"
|
||
#define D_NEOPOOL_JSON_FILTRATION "Filtration"
|
||
#define D_NEOPOOL_JSON_FILTRATION_MODE "Mode"
|
||
#define D_NEOPOOL_JSON_FILTRATION_SPEED "Speed"
|
||
#define D_NEOPOOL_JSON_HYDROLYSIS "Hydrolysis"
|
||
#define D_NEOPOOL_JSON_PERCENT "Percent"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME "Runtime"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME_TOTAL "Total"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME_PART "Part"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME_POL1 "Pol1"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME_POL2 "Pol2"
|
||
#define D_NEOPOOL_JSON_CELL_RUNTIME_CHANGES "Changes"
|
||
#define D_NEOPOOL_JSON_IONIZATION "Ionization"
|
||
#define D_NEOPOOL_JSON_LIGHT "Light"
|
||
#define D_NEOPOOL_JSON_LIGHT_MODE "Mode"
|
||
#define D_NEOPOOL_JSON_REDOX "Redox"
|
||
#define D_NEOPOOL_JSON_RELAY "Relay"
|
||
#define D_NEOPOOL_JSON_RELAY_PH_ACID "Acid"
|
||
#define D_NEOPOOL_JSON_RELAY_PH_BASE "Base"
|
||
#define D_NEOPOOL_JSON_RELAY_RX "Redox"
|
||
#define D_NEOPOOL_JSON_RELAY_CL "Chlorine"
|
||
#define D_NEOPOOL_JSON_RELAY_CD "Conductivity"
|
||
#define D_NEOPOOL_JSON_RELAY_HEATING "Heating"
|
||
#define D_NEOPOOL_JSON_RELAY_UV "UV"
|
||
#define D_NEOPOOL_JSON_RELAY_FILTVALVE "Valve"
|
||
#define D_NEOPOOL_JSON_AUX "Aux"
|
||
#define D_NEOPOOL_JSON_STATE "State"
|
||
#define D_NEOPOOL_JSON_TYPE "Type"
|
||
#define D_NEOPOOL_JSON_UNIT "Unit"
|
||
#define D_NEOPOOL_JSON_COVER "Cover"
|
||
#define D_NEOPOOL_JSON_SHOCK "Boost"
|
||
#define D_NEOPOOL_JSON_OFF "OFF"
|
||
#define D_NEOPOOL_JSON_ON "ON"
|
||
#define D_NEOPOOL_JSON_LOW "Low"
|
||
#define D_NEOPOOL_JSON_SETPOINT "Setpoint"
|
||
#define D_NEOPOOL_JSON_MIN "Min"
|
||
#define D_NEOPOOL_JSON_MAX "Max"
|
||
#define D_NEOPOOL_JSON_PHPUMP "Pump"
|
||
#define D_NEOPOOL_JSON_FLOW1 "FL1"
|
||
#define D_NEOPOOL_JSON_TANK "Tank"
|
||
#define D_NEOPOOL_JSON_BIT "Bit"
|
||
#define D_NEOPOOL_JSON_NODE_ID "NodeID"
|
||
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
#define D_NEOPOOL_JSON_CONNSTAT "Connection"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_REQUESTS "MBRequests"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_0 "MBNoError"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_1 "MBIllegalFunc"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_2 "MBIllegalDataAddr"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_3 "MBIllegalDataValue"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_4 "MBSlaveError"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_5 "MBAck"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_6 "MBSlaveBusy"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_7 "MBNotEnoughData"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_8 "MBMemParityErr"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_9 "MBCRCErr"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_10 "MBGWPath"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_11 "MBGWTarget"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_12 "MBRegErr"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_13 "MBRegData"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_14 "MBTooManyReg"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_15 "MBUnknownErr"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS "MBNoResponse"
|
||
#define D_NEOPOOL_JSON_CONNSTAT_DATA_OOR "DataOutOfRange"
|
||
|
||
const char kNeoPoolMBResults[] PROGMEM =
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_0 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_1 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_2 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_3 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_4 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_5 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_6 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_7 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_8 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_9 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_10 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_11 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_12 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_13 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_14 "|"
|
||
D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS_15
|
||
;
|
||
#endif
|
||
|
||
const char kNeoPoolMachineNames[] PROGMEM =
|
||
D_NEOPOOL_MACH_NONE "|"
|
||
D_NEOPOOL_MACH_HIDROLIFE "|"
|
||
D_NEOPOOL_MACH_AQUASCENIC "|"
|
||
D_NEOPOOL_MACH_OXILIFE "|"
|
||
D_NEOPOOL_MACH_BIONET "|"
|
||
D_NEOPOOL_MACH_HIDRONISER "|"
|
||
D_NEOPOOL_MACH_UVSCENIC "|"
|
||
D_NEOPOOL_MACH_STATION "|"
|
||
D_NEOPOOL_MACH_BRILIX "|"
|
||
D_NEOPOOL_MACH_GENERIC "|"
|
||
D_NEOPOOL_MACH_BAYROL "|"
|
||
D_NEOPOOL_MACH_HAY
|
||
;
|
||
|
||
const char kNeoPoolFiltrationMode[] PROGMEM =
|
||
D_NEOPOOL_FILTRATION_MANUAL "|"
|
||
D_NEOPOOL_FILTRATION_AUTO "|"
|
||
D_NEOPOOL_FILTRATION_HEATING "|"
|
||
D_NEOPOOL_FILTRATION_SMART "|"
|
||
D_NEOPOOL_FILTRATION_INTELLIGENT "|"
|
||
D_NEOPOOL_FILTRATION_BACKWASH
|
||
;
|
||
const char kNeoPoolFiltrationModeCmnd[] PROGMEM =
|
||
D_NEOPOOL_JSON_FILTRATION_MANUAL "|"
|
||
D_NEOPOOL_JSON_FILTRATION_AUTO "|"
|
||
D_NEOPOOL_JSON_FILTRATION_HEATING "|"
|
||
D_NEOPOOL_JSON_FILTRATION_SMART "|"
|
||
D_NEOPOOL_JSON_FILTRATION_INTELLIGENT "|"
|
||
D_NEOPOOL_JSON_FILTRATION_BACKWASH
|
||
;
|
||
const uint8_t sNeoPoolFiltrationMode[] PROGMEM = {
|
||
MBV_PAR_FILT_MANUAL,
|
||
MBV_PAR_FILT_AUTO,
|
||
MBV_PAR_FILT_HEATING,
|
||
MBV_PAR_FILT_SMART,
|
||
MBV_PAR_FILT_INTELLIGENT,
|
||
MBV_PAR_FILT_BACKWASH };
|
||
|
||
const char kNeoPoolFiltrationSpeed[] PROGMEM =
|
||
D_NEOPOOL_FILTRATION_NONE "|"
|
||
D_NEOPOOL_FILTRATION_SLOW "|"
|
||
D_NEOPOOL_FILTRATION_MEDIUM "|"
|
||
D_NEOPOOL_FILTRATION_FAST
|
||
;
|
||
|
||
const char kNeoPoolBoostCmnd[] PROGMEM =
|
||
D_NEOPOOL_JSON_OFF "|"
|
||
D_NEOPOOL_JSON_ON "|"
|
||
D_NEOPOOL_JSON_REDOX
|
||
;
|
||
|
||
const uint16_t sNeoPoolBoost[] PROGMEM = {
|
||
0x0000,
|
||
MBMSK_CELL_BOOST_STATE | MBMSK_CELL_BOOST_START | MBMSK_CELL_BOOST_NO_REDOX_CTL,
|
||
MBMSK_CELL_BOOST_STATE | MBMSK_CELL_BOOST_START };
|
||
|
||
const char kNeoPoolpHAlarms[] PROGMEM =
|
||
D_NEOPOOL_SETPOINT_OK "|"
|
||
D_NEOPOOL_PH_HIGH "|"
|
||
D_NEOPOOL_PH_LOW "|"
|
||
D_NEOPOOL_PUMP_TIME_EXCEEDED "|"
|
||
D_NEOPOOL_PH_HIGH "|"
|
||
D_NEOPOOL_PH_LOW
|
||
;
|
||
|
||
#define NEOPOOL_FMT_PH "%*_f"
|
||
#define NEOPOOL_FMT_RX "%d"
|
||
#define NEOPOOL_FMT_CL "%*_f"
|
||
#define NEOPOOL_FMT_CD "%d"
|
||
#define NEOPOOL_FMT_ION "%*_f"
|
||
#define NEOPOOL_FMT_HIDRO "%*_f"
|
||
|
||
#define D_NEOPOOL_UNIT_PERCENT "%"
|
||
#define D_NEOPOOL_UNIT_GPERH "g/h"
|
||
|
||
const char HTTP_SNS_NEOPOOL_TIME[] PROGMEM = "{s}%s " D_NEOPOOL_TIME "{m}%s" "{e}";
|
||
const char HTTP_SNS_NEOPOOL_VOLTAGE[] PROGMEM = "{s}%s " D_VOLTAGE "{m}%*_f / %*_f / %*_f " D_UNIT_VOLT "{e}";
|
||
const char HTTP_SNS_NEOPOOL_HYDROLYSIS[] PROGMEM = "{s}%s " D_NEOPOOL_HYDROLYSIS "{m}" NEOPOOL_FMT_HIDRO " %s ";
|
||
const char HTTP_SNS_NEOPOOL_PH[] PROGMEM = "{s}%s " D_PH "{m}" NEOPOOL_FMT_PH;
|
||
const char HTTP_SNS_NEOPOOL_REDOX[] PROGMEM = "{s}%s " D_NEOPOOL_REDOX "{m}" NEOPOOL_FMT_RX " " D_UNIT_MILLIVOLT;
|
||
const char HTTP_SNS_NEOPOOL_PPM_CHLORINE[] PROGMEM = "{s}%s " D_NEOPOOL_CHLORINE "{m}" NEOPOOL_FMT_CL " " D_UNIT_PARTS_PER_MILLION "{e}";
|
||
const char HTTP_SNS_NEOPOOL_CONDUCTIVITY[] PROGMEM = "{s}%s " D_NEOPOOL_CONDUCTIVITY "{m}" NEOPOOL_FMT_CD " " D_UNIT_PERCENT "{e}";
|
||
const char HTTP_SNS_NEOPOOL_IONIZATION[] PROGMEM = "{s}%s " D_NEOPOOL_IONIZATION "{m}" NEOPOOL_FMT_ION " " "%s%s" "{e}";
|
||
const char HTTP_SNS_NEOPOOL_FILT_MODE[] PROGMEM = "{s}%s " D_NEOPOOL_FILT_MODE "{m}%s" "{e}";
|
||
const char HTTP_SNS_NEOPOOL_RELAY[] PROGMEM = "{s}%s " "%s" "{m}%s" "{e}";
|
||
const char HTTP_SNS_NEOPOOL_CELL_RUNTIME[] PROGMEM = "{s}%s " D_NEOPOOL_CELL_RUNTIME "{m}%s" "{e}";
|
||
|
||
const char HTTP_SNS_NEOPOOL_STATUS[] PROGMEM = "<span style=\"background-color:%s;font-size:small;text-align:center;%s;\"> %s </span>";
|
||
const char HTTP_SNS_NEOPOOL_STATUS_NORMAL[] PROGMEM = "filter:invert(0.1)";
|
||
const char HTTP_SNS_NEOPOOL_STATUS_DISABLED[] PROGMEM = "display: none";
|
||
const char HTTP_SNS_NEOPOOL_STATUS_INACTIVE[] PROGMEM = "filter:opacity(0.15)";
|
||
const char HTTP_SNS_NEOPOOL_STATUS_ACTIVE[] PROGMEM = "filter:invert(1)";
|
||
|
||
|
||
/****************************************************************************\
|
||
* Commands
|
||
*
|
||
* NPFiltration {<state> {speed}}
|
||
* get/set manual filtration (state = 0..2, speed = 1..3)
|
||
* get filtration state if <state> is omitted,
|
||
* otherwise set new state
|
||
* 0 - switch filtration pump off
|
||
* 1 - switch filtration pump on
|
||
* 2 - toggle filtration pump
|
||
* additional speed control is possible for non-standardised
|
||
* filter types
|
||
*
|
||
* NPFiltrationMode {<mode>}
|
||
* get/set filtration mode (mode = 0..4|13)
|
||
* get mode if <mode> is omitted,
|
||
* otherwise set new mode according:
|
||
* 0 - Manual
|
||
* 1 - Auto
|
||
* 2 - Heating
|
||
* 3 - Smart
|
||
* 4 - Intelligent
|
||
* 13 - Backwash
|
||
*
|
||
* NPFiltrationSpeed {<speed>}
|
||
* (only available for non-standard filtration types)
|
||
* get/set manual filtration speed (speed = 1..3)
|
||
* get filtration speed if <speed> is omitted,
|
||
* otherwise set new speed:
|
||
* 1 - low
|
||
* 2 - mid
|
||
* 3 - high
|
||
*
|
||
* NPBoost {<mode>}
|
||
* get/set hydrolysis/electrolysis boost mode (mode = 0..2)
|
||
* get mode if <mode> is omitted,
|
||
* otherwise set new mode according:
|
||
* 0|OFF - boost off
|
||
* 1|ON - boost on
|
||
* 2|REDOX - boost on with redox control
|
||
*
|
||
* NPTime {<time>}
|
||
* get/set system time
|
||
* get current time if <time> is omitted,
|
||
* otherwise set time according:
|
||
* 0 - sync with Tasmota local time
|
||
* 1 - sync with Tasmota utc time
|
||
* any other value of <time> will set time as epoch
|
||
*
|
||
* NPLight {<state> {delay}}
|
||
* get/set light (state = 0|1|2|3|4, delay = 5..100)
|
||
* get light state if <state> is omitted, otherwise set new state
|
||
* 0 - switch light manual off
|
||
* 1 - switch light manual on
|
||
* 2 - toggle light
|
||
* 3 - switch light to auto mode
|
||
* 4 - switch to next program for RGB-LED lights using <delay> in
|
||
* 1/10 sec (5 = 0.5 sec, 100 = 10 sec)
|
||
* Change LED program by switching light relay OFF for <delay>
|
||
* time, then switch light relay ON. If the light was
|
||
* originally OFF, it is switched ON first.
|
||
*
|
||
* NPpHMin {<ph>}
|
||
* (only available if pH module is installed)
|
||
* get/set pH lower limit (ph = 0..14)
|
||
* get current limit if <ph> is omitted, otherwise set
|
||
*
|
||
* NPpHMax {<ph>}
|
||
* (only available if pH module is installed)
|
||
* get/set pH upper limit (ph = 0..14)
|
||
* get current limit if <ph> is omitted, otherwise set
|
||
*
|
||
* NPpH {<ph>}
|
||
* (only available if pH module is installed)
|
||
* get/set pH upper limit (ph = 0..14)
|
||
* same as NPpHMax
|
||
*
|
||
* NPRedox {<setpoint>}
|
||
* (only available if redox module is installed)
|
||
* get/set redox set point in mV (setpoint = 0..100
|
||
* get current set point if <setpoint> is omitted, otherwise set
|
||
*
|
||
* NPHydrolysis {<level> {%}}
|
||
* (only available if hydrolysis/electrolysis control is present)
|
||
* get/set hydrolysis/electrolysis level
|
||
* get current level if <level> is omitted, otherwise set:
|
||
* 0..100 in % for NeoPool systems configured to %
|
||
* 0..<max> in g/h for NeoPool systems configured for g/h
|
||
* (<max> depends by M_PAR_HIDRO_NOM register value)
|
||
* <level> can specified in % on all NeoPool systems by appending
|
||
* the % sign to the value
|
||
*
|
||
* NPIonization {<level>}
|
||
* (only available if ionization control is present)
|
||
* get/set ionization target production level
|
||
* (level = 0..x, the upper limit of the range may vary depending
|
||
* on the MBF_PAR_ION_NOM register)
|
||
* get current level if <level> is omitted, otherwise set
|
||
*
|
||
* NPChlorine {<setpoint>}
|
||
* (only available if free chlorine probe detector is installed)
|
||
* get/set chlorine set point in ppm (setpoint = 0..10)
|
||
* get current set point if <setpoint> is omitted, otherwise set
|
||
*
|
||
* NPControl
|
||
* Show information about system controls
|
||
*
|
||
* NPTelePeriod {time}
|
||
* enables/disables auto telemetry SENSOR message when NeoPool
|
||
* values change (time = 0 or 5..3600):
|
||
* 0 disable this function off (default), SENSOR messages are
|
||
* only reported depending on TelePeriod setting
|
||
* 5..3600 set the minimum of seconds between two SENSOR messages
|
||
* for NeoPool measured (sensor) values
|
||
* (status changes for relays and settings trigger the
|
||
* SENSOR messages immediately, regardless of this time)
|
||
* If <time> is set higher than TelePeriod, only status changes for
|
||
* relays and settings will trigger SENSOR message.
|
||
*
|
||
* NPSave
|
||
* write data permanently into EEPROM
|
||
*
|
||
* NPExec
|
||
* immediately take over changed data (without writing to EEPROM)
|
||
*
|
||
* NPEscape
|
||
* clears possible errors (like pump exceeded time etc.)
|
||
*
|
||
* NPResult {<format>}
|
||
* get/set addr/data result format read/write commands
|
||
* (format = 0|1)
|
||
* get output format if <format> is omitted, otherwise
|
||
* 0 - output as decimal numbers
|
||
* 1 - output as hexadecimal strings (default)
|
||
*
|
||
* NPOnError {<repeat>}
|
||
* get/set auto-repeat Modbus read/write commands on error
|
||
* (repeat = 0..10)
|
||
* get auto-repeat setting if <repeat> is omitted, otherwise
|
||
* 0 - disable auto-repeat on read/write error
|
||
* 1..10 - repeat commands n times until ok
|
||
*
|
||
* NPPHRes {<digits>}
|
||
* NPCLRes {<digits>}
|
||
* NPIonRes {<digits>}
|
||
* get/set number of digits in results for PH, CL and ION values
|
||
*
|
||
* NPSetOption0 {0|1}
|
||
* (only available on ESP32 or if NEOPOOL_RANGE_CHECKS is defined)
|
||
* Disable(0)/enable(1) sensor data min/max validation and
|
||
* correction
|
||
*
|
||
* NPSetOption1 {0|1}
|
||
* (only available on ESP32 or if NEOPOOL_CONNSTAT is defined)
|
||
* Disable(0)/enable(1) modbus connection statistics
|
||
*
|
||
* NPRead <addr> {<cnt>}
|
||
* NPReadL <addr> {<cnt>}
|
||
* read 16/32-bit register (cnt = 1..30|1..15), cnt = 1 if omitted
|
||
* NPRead read 16-bit register
|
||
* NPReadL read 32-bit register
|
||
*
|
||
* NPWrite <addr> <data> {<data>...}
|
||
* NPWriteL <addr> <data> {<data>...}
|
||
* NPWrite write 16-bit register (data = 0..65535)
|
||
* NPWriteL write 32-bit register (data = 0..4294967295)
|
||
* The max. number of <data> parameters is 10
|
||
*
|
||
* NPBit <addr> <bit> {<data>}
|
||
* NPBitL <addr> <bit> {<data>}
|
||
* read/write register bit (bit = 0..15, data = 0|1)
|
||
* read if <data> is omitted, otherwise set <bit> to new <data>
|
||
*
|
||
*
|
||
* Note:
|
||
* The setttings changed by commands NPPHRes, NPCLRes, NPIonRes,
|
||
* NPSetOption0 and NPSetOption1 are permanently stored only if firmware was
|
||
* compiled with USE_UFILESYS (default enabled on ESP32 and disabled on
|
||
* ESP82xx). Without USE_UFILESYS (default on ESP82xx), you can alternatively
|
||
* use a rule to set your defaults during system start, e. g.:
|
||
* Rule1 ON System#Init DO Backlog NPPHRes 1;NPCLRes 1;NPIonRes 1;NPSetOption0 1;NPSetOption1 0
|
||
*
|
||
*
|
||
* Command examples:
|
||
*
|
||
* Get/Set filtration mode
|
||
* NPFiltrationMode
|
||
* RESULT = {"NPFiltrationmode":"Manual"}
|
||
* NPFiltrationMode 1
|
||
* {"NPFiltrationmode":"Auto"}
|
||
*
|
||
* Switch light relay on
|
||
* NPLight 1
|
||
* RESULT = {"NPLight":"ON"}
|
||
*
|
||
* Read Heating setpoint temperature MBF_PAR_HEATING_TEMP
|
||
* Backlog NPResult 0;NPRead 0x416
|
||
* RESULT = {"NPResult":0}
|
||
* RESULT = {"NPRead":{"Address":1046,"Data":28}}
|
||
*
|
||
* Read system time MBF_PAR_TIME_* as 32-bit register using decimal output
|
||
* Backlog NPResult 0;NPReadL 0x408
|
||
* RESULT = {"NPResult":0}
|
||
* RESULT = {"NPReadL":{"Address":1032,"Data":1612124540}}
|
||
*
|
||
* Enable temperature module by setting MBF_PAR_TEMPERATURE_ACTIVE
|
||
* and set it permanently into EEPROM
|
||
* Backlog NPWrite 0x40F,1;NPSave
|
||
* RESULT = {"NPWrite":{"Address":"0x040F","Data":"0x0001"}}
|
||
* RESULT = {"NPSave":"Done"}
|
||
*
|
||
* Hide auxiliary relay display from main menu
|
||
* by setting bit 3 of MBF_PAR_UICFG_VISUAL_OPTIONS
|
||
* NPBit 0x605,3,1
|
||
* RESULT = {"NPBit":{"Address":"0x0605","Data":"0x08C8"}}
|
||
*
|
||
* Read Filtration interval 1-3 settings
|
||
* Backlog NPResult 0;NPRead 0x434;NPReadL 0x435,7;NPRead 0x443;NPReadL 0x444,7;NPRead 0x452;NPReadL 0x0453,7
|
||
* RESULT = {"NPResult":0}
|
||
* RESULT = {"NPRead":{"Address":1076,"Data":1}}
|
||
* RESULT = {"NPReadL":{"Address":1077,"Data":[28800,0,86400,14400,0,1,0]}}
|
||
* RESULT = {"NPRead":{"Address":1091,"Data":1}}
|
||
* RESULT = {"NPReadL":{"Address":1092,"Data":[43200,0,86400,21600,0,1,0]}}
|
||
* RESULT = {"NPRead":{"Address":1106,"Data":1}}
|
||
* RESULT = {"NPReadL":{"Address":1107,"Data":[0,0,86400,0,0,1,0]}} *
|
||
*
|
||
* Set filtration interval 1 to daily 9:00 - 12:30
|
||
* (9:00: 3600 * 9 ≙ 32400 / 12:30 ≙ 3,5h = 12600)
|
||
* NPWriteL 0x435,32400 0 86400 12600
|
||
* RESULT = {"NPWriteL":{"Address":1077,"Data":[32400,0,86400,12600]}}
|
||
*
|
||
****************************************************************************/
|
||
|
||
#define D_PRFX_NEOPOOL "NP"
|
||
#define D_CMND_NP_RESULT "Result"
|
||
#define D_CMND_NP_READ "Read"
|
||
#define D_CMND_NP_READL "ReadL"
|
||
#define D_CMND_NP_WRITE "Write"
|
||
#define D_CMND_NP_WRITEL "WriteL"
|
||
#define D_CMND_NP_BIT "Bit"
|
||
#define D_CMND_NP_BITL "BitL"
|
||
#define D_CMND_NP_FILTRATION "Filtration"
|
||
#define D_CMND_NP_FILTRATIONMODE "Filtrationmode"
|
||
#define D_CMND_NP_FILTRATIONSPEED "Filtrationspeed"
|
||
#define D_CMND_NP_BOOST "Boost"
|
||
#define D_CMND_NP_TIME "Time"
|
||
#define D_CMND_NP_LIGHT "Light"
|
||
#define D_CMND_NP_PHMIN "pHMin"
|
||
#define D_CMND_NP_PHMAX "pHMax"
|
||
#define D_CMND_NP_PH "pH"
|
||
#define D_CMND_NP_REDOX "Redox"
|
||
#define D_CMND_NP_HYDROLYSIS "Hydrolysis"
|
||
#define D_CMND_NP_IONIZATION "Ionization"
|
||
#define D_CMND_NP_CHLORINE "Chlorine"
|
||
#define D_CMND_NP_CONTROL "Control"
|
||
#define D_CMND_NP_TELEPERIOD "TelePeriod"
|
||
#define D_CMND_NP_SAVE "Save"
|
||
#define D_CMND_NP_EXEC "Exec"
|
||
#define D_CMND_NP_ESCAPE "Escape"
|
||
#define D_CMND_NP_ONERROR "OnError"
|
||
#define D_CMND_NP_PHRES "PHRes"
|
||
#define D_CMND_NP_CLRES "CLRes"
|
||
#define D_CMND_NP_IONRES "IONRes"
|
||
#define D_CMND_NP_SETOPTION "SetOption"
|
||
#define D_CMND_NP_SO "SO"
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
#define D_CMND_NP_GPERH "gPerh"
|
||
#endif
|
||
|
||
const char kNPCommands[] PROGMEM = D_PRFX_NEOPOOL "|" // Prefix
|
||
D_CMND_NP_RESULT "|"
|
||
D_CMND_NP_READ "|"
|
||
D_CMND_NP_READL "|"
|
||
D_CMND_NP_WRITE "|"
|
||
D_CMND_NP_WRITEL "|"
|
||
D_CMND_NP_BIT "|"
|
||
D_CMND_NP_BITL "|"
|
||
D_CMND_NP_FILTRATION "|"
|
||
D_CMND_NP_FILTRATIONMODE "|"
|
||
D_CMND_NP_FILTRATIONSPEED "|"
|
||
D_CMND_NP_BOOST "|"
|
||
D_CMND_NP_TIME "|"
|
||
D_CMND_NP_LIGHT "|"
|
||
D_CMND_NP_PHMIN "|"
|
||
D_CMND_NP_PHMAX "|"
|
||
D_CMND_NP_PH "|"
|
||
D_CMND_NP_REDOX "|"
|
||
D_CMND_NP_HYDROLYSIS "|"
|
||
D_CMND_NP_IONIZATION "|"
|
||
D_CMND_NP_CHLORINE "|"
|
||
D_CMND_NP_CONTROL "|"
|
||
D_CMND_NP_TELEPERIOD "|"
|
||
D_CMND_NP_SAVE "|"
|
||
D_CMND_NP_EXEC "|"
|
||
D_CMND_NP_ESCAPE "|"
|
||
D_CMND_NP_ONERROR "|"
|
||
D_CMND_NP_PHRES "|"
|
||
D_CMND_NP_CLRES "|"
|
||
D_CMND_NP_IONRES "|"
|
||
D_CMND_NP_SETOPTION "|"
|
||
D_CMND_NP_SO
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
"|" D_CMND_NP_GPERH
|
||
#endif
|
||
;
|
||
|
||
void (* const NPCommand[])(void) PROGMEM = {
|
||
&CmndNeopoolResult,
|
||
&CmndNeopoolReadReg,
|
||
&CmndNeopoolReadReg,
|
||
&CmndNeopoolWriteReg,
|
||
&CmndNeopoolWriteReg,
|
||
&CmndNeopoolBit,
|
||
&CmndNeopoolBit,
|
||
&CmndNeopoolFiltration,
|
||
&CmndNeopoolFiltrationMode,
|
||
&CmndNeopoolFiltrationSpeed,
|
||
&CmndNeopoolBoost,
|
||
&CmndNeopoolTime,
|
||
&CmndNeopoolLight,
|
||
&CmndNeopoolpHMin,
|
||
&CmndNeopoolpHMax,
|
||
&CmndNeopoolpHMax,
|
||
&CmndNeopoolRedox,
|
||
&CmndNeopoolHydrolysis,
|
||
&CmndNeopoolIonization,
|
||
&CmndNeopoolChlorine,
|
||
&CmndNeopoolControl,
|
||
&CmndNeopoolTelePeriod,
|
||
&CmndNeopoolSave,
|
||
&CmndNeopoolExec,
|
||
&CmndNeopoolEscape,
|
||
&CmndNeopoolOnError,
|
||
&CmndNeopoolPHRes,
|
||
&CmndNeopoolCLRes,
|
||
&CmndNeopoolIONRes,
|
||
&CmndNeopoolSetOption,
|
||
&CmndNeopoolSetOption
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
,&CmndNeopoolgPerh
|
||
#endif
|
||
};
|
||
|
||
|
||
|
||
/****************************************************************************/
|
||
|
||
void NeoPoolPoll(void) // Poll modbus register
|
||
{
|
||
// called every 250 ms
|
||
if (!neopool_poll) {
|
||
return;
|
||
};
|
||
|
||
if (neopoll_cmd_delay) {
|
||
neopoll_cmd_delay--;
|
||
if (0 == neopoll_cmd_delay && nullptr != neopoll_cmd) {
|
||
void (* do_cmd)(void) = neopoll_cmd;
|
||
neopoll_cmd = nullptr;
|
||
do_cmd();
|
||
}
|
||
}
|
||
|
||
bool data_ready = NeoPoolModbus->ReceiveReady();
|
||
|
||
if (data_ready && nullptr != NeoPoolReg[neopool_read_state].data) {
|
||
uint8_t *buffer = (uint8_t *)malloc(5+(NeoPoolReg[neopool_read_state].cnt)*2);
|
||
|
||
if (nullptr != buffer) {
|
||
uint8_t error = NeoPoolModbus->ReceiveBuffer(buffer, NeoPoolReg[neopool_read_state].cnt); // cnt x 16bit register
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolModbusErrorCount(error);
|
||
#endif
|
||
if (0 == error) {
|
||
neopool_failed_count = 0;
|
||
neopool_error = false;
|
||
for (uint32_t i = 0; i < NeoPoolReg[neopool_read_state].cnt; i++) {
|
||
NeoPoolReg[neopool_read_state].data[i] = (buffer[i*2+3] << 8) | buffer[i*2+4];
|
||
}
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: modbus receive error %d"), error);
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
free(buffer);
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: modbus block 0x%04X - 0x%04X skipped"), NeoPoolReg[neopool_read_state].addr, NeoPoolReg[neopool_read_state].addr+NeoPoolReg[neopool_read_state].cnt);
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
|
||
++neopool_read_state %= nitems(NeoPoolReg);
|
||
}
|
||
|
||
if (nullptr != NeoPoolReg[neopool_read_state].data) {
|
||
if (0 == neopool_send_retry || data_ready) {
|
||
neopool_send_retry = SENSOR_MAX_MISS; // controller sometimes takes long time to answer
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: modbus send(%d, %d, 0x%04X, %d)"), NEOPOOL_MODBUS_ADDRESS, NEOPOOL_READ_REGISTER, NeoPoolReg[neopool_read_state].addr, NeoPoolReg[neopool_read_state].cnt);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPoolModbus->Send(NEOPOOL_MODBUS_ADDRESS, NEOPOOL_READ_REGISTER, NeoPoolReg[neopool_read_state].addr, NeoPoolReg[neopool_read_state].cnt);
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolStats.mb_requests++;
|
||
#endif
|
||
} else {
|
||
if (1 == neopool_send_retry) {
|
||
neopool_failed_count++;
|
||
if (neopool_failed_count > 2) {
|
||
neopool_failed_count = 0;
|
||
neopool_error = true;
|
||
NeoPoolInitData();
|
||
}
|
||
}
|
||
neopool_send_retry--;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/****************************************************************************/
|
||
|
||
void NeoPoolInit(void) {
|
||
NeoPoolSettingsLoad(false);
|
||
neopool_active = false;
|
||
if (PinUsed(GPIO_NEOPOOL_RX) && PinUsed(GPIO_NEOPOOL_TX)) {
|
||
NeoPoolModbus = new TasmotaModbus(Pin(GPIO_NEOPOOL_RX), Pin(GPIO_NEOPOOL_TX));
|
||
uint8_t result = NeoPoolModbus->Begin(NEOPOOL_MODBUS_SPEED);
|
||
if (result) {
|
||
if (2 == result) {
|
||
ClaimSerial();
|
||
}
|
||
if (NeoPoolInitData()) { // Claims heap space
|
||
neopool_active = true;
|
||
}
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: NeoPoolInit Modbus init failed %d"), result);
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: NeoPoolInit no GPIOs assigned"));
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
|
||
}
|
||
|
||
|
||
bool NeoPoolInitData(void)
|
||
{
|
||
bool result = true;
|
||
|
||
neopool_error = true;
|
||
neopool_power_module_version = 0;
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
memset(&NeoPoolStats, 0, sizeof(NeoPoolStats));
|
||
#endif
|
||
memset(neopool_power_module_nodeid, 0, sizeof(neopool_power_module_nodeid));
|
||
|
||
for (uint32_t i = 0; i < nitems(NeoPoolReg); i++) {
|
||
if (nullptr == NeoPoolReg[i].data) {
|
||
NeoPoolReg[i].data = (uint16_t *)malloc(sizeof(uint16_t)*NeoPoolReg[i].cnt);
|
||
if (nullptr != NeoPoolReg[i].data) {
|
||
memset(NeoPoolReg[i].data, 0, sizeof(uint16_t)*NeoPoolReg[i].cnt);
|
||
}
|
||
else {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NeoPoolInitData - NeoPoolReg[%d].data out of memory"), i);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
result = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!result) {
|
||
// release partially reserved memory on init error
|
||
for (uint32_t i = 0; i < nitems(NeoPoolReg); i++) {
|
||
if (nullptr != NeoPoolReg[i].data) {
|
||
free(NeoPoolReg[i].data);
|
||
NeoPoolReg[i].data = nullptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
|
||
/****************************************************************************/
|
||
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
void NeoPoolLogRW(const char *name, uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
char *log_data = (char *)malloc(cnt*7+1);
|
||
*log_data = 0;
|
||
for (uint32_t i = 0; i < cnt; i++) {
|
||
char h[8];
|
||
snprintf_P(h, sizeof(h), PSTR("%s0x%04X"), i ? PSTR(",") : PSTR(""), data[i]);
|
||
strncat(log_data, h, cnt*7+1);
|
||
}
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: %s(0x%04X, %d) = [%s]"), name, addr, cnt, log_data);
|
||
free(log_data);
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
|
||
|
||
void NeoPool250msSetStatus(bool status)
|
||
{
|
||
neopool_poll = status;
|
||
|
||
if (!status) {
|
||
NeoPoolModbus->flush();
|
||
// clear rec buffer from possible prev periodical communication
|
||
uint32_t timeoutMS = millis() + 100 * NEOPOOL_READ_TIMEOUT; // Max delay before we timeout
|
||
while (NeoPoolModbus->available() && millis() < timeoutMS) {
|
||
NeoPoolModbus->read();
|
||
SleepDelay(0);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
void NeoPoolModbusErrorCount(uint8_t error)
|
||
{
|
||
if (NeoPoolStats.time < 86400L) {
|
||
NeoPoolStats.time = Rtc.local_time;
|
||
}
|
||
if (error < nitems(NeoPoolStats.mb_results) - 1) {
|
||
NeoPoolStats.mb_results[error]++;
|
||
} else {
|
||
NeoPoolStats.mb_results[nitems(NeoPoolStats.mb_results) - 1]++;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
uint8_t NeoPoolReadRegisterData(uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
bool data_ready;
|
||
uint32_t timeoutMS;
|
||
uint16_t *origin = data;
|
||
|
||
NeoPool250msSetStatus(false);
|
||
*data = 0;
|
||
|
||
NeoPoolModbus->Send(NEOPOOL_MODBUS_ADDRESS, NEOPOOL_READ_REGISTER, addr, cnt);
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolStats.mb_requests++;
|
||
#endif
|
||
timeoutMS = millis() + cnt * NEOPOOL_READ_TIMEOUT; // Max delay before we timeout
|
||
while (!(data_ready = NeoPoolModbus->ReceiveReady()) && millis() < timeoutMS) { delay(1); }
|
||
if (data_ready) {
|
||
uint8_t *buffer = (uint8_t*)malloc(5+cnt*2);
|
||
if (buffer != nullptr) {
|
||
uint8_t error = NeoPoolModbus->ReceiveBuffer(buffer, cnt);
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolModbusErrorCount(error);
|
||
#endif
|
||
if (error) {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X read data error %d"), addr, error);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPool250msSetStatus(true);
|
||
free(buffer);
|
||
return NEOPOOL_MODBUS_ERROR_RW_DATA;
|
||
}
|
||
for(uint64_t i = 0; i < cnt; i++) {
|
||
*data++ = (buffer[i*2+3] << 8) | buffer[i*2+4];
|
||
}
|
||
NeoPool250msSetStatus(true);
|
||
delay(2);
|
||
free(buffer);
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
NeoPoolLogRW("NeoPoolReadRegister", addr, origin, cnt);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
return NEOPOOL_MODBUS_OK;
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X read out of memory"), addr);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
return NEOPOOL_MODBUS_ERROR_OUT_OF_MEM;
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X read data timeout"), addr);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPool250msSetStatus(true);
|
||
return NEOPOOL_MODBUS_ERROR_TIMEOUT;
|
||
}
|
||
|
||
|
||
uint8_t NeoPoolWriteRegisterData(uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
uint8_t *frame;
|
||
uint32_t numbytes;
|
||
bool data_ready;
|
||
uint32_t timeoutMS;
|
||
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
NeoPoolLogRW("NeoPoolWriteRegister", addr, data, cnt);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPool250msSetStatus(false);
|
||
numbytes = 7+cnt*2;
|
||
frame = (uint8_t*)malloc(numbytes+2);
|
||
if (nullptr == frame) {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X write out of memory"), addr);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
return NEOPOOL_MODBUS_ERROR_OUT_OF_MEM;
|
||
}
|
||
|
||
// Function 16 (10hex) Write Multiple Registers
|
||
// Header
|
||
frame[0] = NEOPOOL_MODBUS_ADDRESS;
|
||
frame[1] = NEOPOOL_WRITE_REGISTER;
|
||
frame[2] = (uint8_t)(addr >> 8); // addr MSB
|
||
frame[3] = (uint8_t)(addr); // addr LSB
|
||
frame[4] = (uint8_t)(cnt >> 8); // register quantity MSB
|
||
frame[5] = (uint8_t)(cnt); // register quantity LSB
|
||
frame[6] = (uint8_t)(cnt * 2); // byte count
|
||
for (uint32_t i = 0; i < cnt; i++) {
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
frame[7+i*2] = (uint8_t)(NeoPoolEmulateSetData(addr + i, data[i]) >> 8); // data MSB
|
||
frame[8+i*2] = (uint8_t)(NeoPoolEmulateSetData(addr + i, data[i])); // data LSB
|
||
#else
|
||
frame[7+i*2] = (uint8_t)(data[i] >> 8); // data MSB
|
||
frame[8+i*2] = (uint8_t)(data[i]); // data LSB
|
||
#endif
|
||
}
|
||
uint16_t crc = NeoPoolModbus->CalculateCRC(frame, numbytes);
|
||
frame[numbytes] = (uint8_t)(crc);
|
||
frame[numbytes+1] = (uint8_t)(crc >> 8);
|
||
|
||
NeoPoolModbus->flush();
|
||
NeoPoolModbus->write(frame, numbytes+2);
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolStats.mb_requests++;
|
||
#endif
|
||
|
||
timeoutMS = millis() + 1 * NEOPOOL_READ_TIMEOUT; // Max delay before we timeout
|
||
while (!(data_ready = NeoPoolModbus->ReceiveReady()) && millis() < timeoutMS) { delay(1); }
|
||
free(frame);
|
||
if (data_ready) {
|
||
uint8_t buffer[9];
|
||
uint8_t error = NeoPoolModbus->ReceiveBuffer(buffer, 1);
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolModbusErrorCount(error);
|
||
#endif
|
||
if (0 != error && 9 != error) { // ReceiveBuffer can't handle 0x10 code result
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X write data response error %d"), addr, error);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPool250msSetStatus(true);
|
||
return NEOPOOL_MODBUS_ERROR_RW_DATA;
|
||
}
|
||
if (9 == error) {
|
||
// clear buffer before we leave
|
||
while (NeoPoolModbus->available()) {
|
||
NeoPoolModbus->read();
|
||
}
|
||
}
|
||
NeoPool250msSetStatus(true);
|
||
delay(2);
|
||
if (MBF_SAVE_TO_EEPROM == addr) {
|
||
// EEPROM write can take some time, wait until device is ready
|
||
timeoutMS = millis() + 1000; // Max delay for save eeprom cmnd
|
||
uint16_t tmp;
|
||
while (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_NOTIFICATION, &tmp, 1) && millis() < timeoutMS);
|
||
}
|
||
return NEOPOOL_MODBUS_OK;
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: addr 0x%04X write data response timeout"), addr);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPool250msSetStatus(true);
|
||
return NEOPOOL_MODBUS_ERROR_TIMEOUT;
|
||
}
|
||
|
||
|
||
uint8_t NeoPoolReadRegisterRaw(uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
uint8_t repeat = neopool_repeat_on_error;
|
||
uint8_t result;
|
||
do {
|
||
result = NeoPoolReadRegisterData(addr, data, cnt);
|
||
SleepDelay(0);
|
||
} while(repeat-- > 0 && NEOPOOL_MODBUS_OK != result);
|
||
return result;
|
||
}
|
||
|
||
uint8_t NeoPoolReadRegister(uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
uint8_t result = NeoPoolReadRegisterRaw(addr, data, cnt);
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
if (NEOPOOL_MODBUS_OK == result) {
|
||
for (uint32_t i = 0; i < cnt; i++) {
|
||
data[i] = NeoPoolEmulateGetData(addr + i, data[i]);
|
||
}
|
||
}
|
||
#endif
|
||
return result;
|
||
}
|
||
|
||
|
||
uint8_t NeoPoolWriteRegister(uint16_t addr, uint16_t *data, uint16_t cnt)
|
||
{
|
||
uint8_t repeat = neopool_repeat_on_error;
|
||
uint8_t result;
|
||
|
||
do {
|
||
result = NeoPoolWriteRegisterData(addr, data, cnt);
|
||
SleepDelay(0);
|
||
} while(repeat-- > 0 && NEOPOOL_MODBUS_OK != result);
|
||
return result;
|
||
}
|
||
|
||
|
||
uint8_t NeoPoolWriteRegisterWord(uint16_t addr, uint16_t data)
|
||
{
|
||
return NeoPoolWriteRegister(addr, &data, 1);
|
||
}
|
||
|
||
|
||
uint16_t NeoPoolGetCacheData(uint16_t addr, int32_t timeout)
|
||
{
|
||
uint16_t data = 0;
|
||
bool datavalid = false;
|
||
uint16_t i;
|
||
|
||
// search in regular data storage
|
||
for (i = 0; !datavalid && i < nitems(NeoPoolReg); i++) {
|
||
if (nullptr != NeoPoolReg[i].data && addr >= NeoPoolReg[i].addr && addr < NeoPoolReg[i].addr+NeoPoolReg[i].cnt) {
|
||
data = NeoPoolReg[i].data[addr - NeoPoolReg[i].addr];
|
||
datavalid = true;
|
||
}
|
||
}
|
||
|
||
if (!datavalid) {
|
||
// not found in regular data storage, search within cache
|
||
if (timeout < 0) {
|
||
timeout = NEOPOOL_CACHE_INVALID_TIME * 1000;
|
||
}
|
||
for (i = 0; !datavalid && i < NeoPoolDataCount; i++) {
|
||
if (nullptr != NeoPoolData && addr == NeoPoolData[i].addr) {
|
||
if (millis() < NeoPoolData[i].ts) {
|
||
data = NeoPoolData[i].data;
|
||
} else {
|
||
NeoPoolReadRegisterRaw(addr, &data, 1);
|
||
NeoPoolData[i].data = data;
|
||
NeoPoolData[i].ts = millis() + timeout;
|
||
}
|
||
datavalid = true;
|
||
}
|
||
}
|
||
|
||
if (!datavalid) {
|
||
// no cache hit, read origin from modbus register and store result within cache
|
||
NeoPoolReadRegisterRaw(addr, &data, 1);
|
||
datavalid = true;
|
||
if (nullptr == NeoPoolData) {
|
||
NeoPoolDataCount = 1;
|
||
NeoPoolData = (TNeoPoolData*)malloc(sizeof(TNeoPoolData) * NeoPoolDataCount);
|
||
} else {
|
||
NeoPoolDataCount++;
|
||
NeoPoolData = (TNeoPoolData*)realloc(NeoPoolData, sizeof(TNeoPoolData) * NeoPoolDataCount);
|
||
}
|
||
if (nullptr != NeoPoolData) {
|
||
NeoPoolData[NeoPoolDataCount-1].addr = addr;
|
||
NeoPoolData[NeoPoolDataCount-1].data = data;
|
||
NeoPoolData[NeoPoolDataCount-1].ts = millis() + timeout;
|
||
} else {
|
||
NeoPoolDataCount = 0;
|
||
}
|
||
}
|
||
}
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
return NeoPoolEmulateGetData(addr, data);
|
||
#else
|
||
return data;
|
||
#endif
|
||
}
|
||
|
||
|
||
uint16_t NeoPoolGetData(uint16_t addr)
|
||
{
|
||
uint16_t data = NeoPoolGetCacheData(addr, -1);
|
||
|
||
#ifdef NEOPOOL_RANGE_CHECKS
|
||
if (NeoPoolSettings.flags.range_check) {
|
||
for (uint16_t i = 0; i < nitems(NeoPoolRangeCheck); i++) {
|
||
if (MBF_HIDRO_CURRENT == NeoPoolRangeCheck[i].addr && NEOPOOL_UNDEF_UINT16 == NeoPoolRangeCheck[i].max) {
|
||
// get hydrolsysis max value
|
||
uint16_t max = NeoPoolGetCacheData(MBF_PAR_HIDRO_NOM, -1);
|
||
if (0 != max) {
|
||
NeoPoolRangeCheck[i].max = max;
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: ConnStat - use hydrolysis max = %d"), NeoPoolRangeCheck[i].max);
|
||
#endif
|
||
}
|
||
}
|
||
if (NeoPoolRangeCheck[i].addr == addr) {
|
||
uint16_t prev_data = data;
|
||
// check out of range
|
||
if (data < NeoPoolRangeCheck[i].min || data > NeoPoolRangeCheck[i].max) {
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
NeoPoolStats.value_out_of_range++;
|
||
#endif
|
||
// use previous value if defined
|
||
if (NEOPOOL_UNDEF_UINT16 != NeoPoolRangeCheck[i].prev) {
|
||
data = NeoPoolRangeCheck[i].prev;
|
||
} else {
|
||
// limit to min/max as long as no valid previous value is present
|
||
if (data < NeoPoolRangeCheck[i].min) {
|
||
data = NeoPoolRangeCheck[i].min;
|
||
} else {
|
||
data = NeoPoolRangeCheck[i].max;
|
||
}
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: ConnStat - Addr 0x%04X data out of range [%d-%d]: received %d, corrected using %d"),
|
||
NeoPoolRangeCheck[i].addr,
|
||
NeoPoolRangeCheck[i].min,
|
||
NeoPoolRangeCheck[i].max,
|
||
prev_data,
|
||
data);
|
||
#endif
|
||
}
|
||
else {
|
||
// remeber origin value
|
||
NeoPoolRangeCheck[i].prev = data;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif // NEOPOOL_RANGE_CHECKS
|
||
return data;
|
||
}
|
||
|
||
|
||
uint32_t NeoPoolGetDataLong(uint16_t addr)
|
||
{
|
||
return ((uint32_t)NeoPoolGetData(addr) + ((uint32_t)NeoPoolGetData(addr+1) << 16));
|
||
}
|
||
|
||
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
uint16_t NeoPoolEmulateGetData(uint16_t addr, uint16_t data)
|
||
{
|
||
if (neopool_system_gperh) {
|
||
// emulate g/h device
|
||
switch(addr) {
|
||
case MBF_PAR_UICFG_MACH_VISUAL_STYLE:
|
||
data &= ~((MBMSK_VS_FORCE_UNITS_GRH | MBMSK_VS_FORCE_UNITS_PERCENTAGE));
|
||
data |= MBMSK_VS_FORCE_UNITS_GRH;
|
||
break;
|
||
case MBF_HIDRO_CURRENT:
|
||
case MBF_PAR_HIDRO:
|
||
case MBF_PAR_HIDRO_NOM:
|
||
// [0..1000] -> [0(NEOPOOL_EMULATE_GPERH * 10)
|
||
data = data * NEOPOOL_EMULATE_GPERH / 100;
|
||
break;
|
||
}
|
||
}
|
||
return data;
|
||
}
|
||
|
||
uint16_t NeoPoolEmulateSetData(uint16_t addr, uint16_t data)
|
||
{
|
||
if (neopool_system_gperh) {
|
||
// emulate g/h device
|
||
switch(addr) {
|
||
case MBF_PAR_HIDRO:
|
||
// [0..(NEOPOOL_EMULATE_GPERH * 10)] -> [0..1000]
|
||
data = (data * 100) / NEOPOOL_EMULATE_GPERH;
|
||
break;
|
||
}
|
||
}
|
||
return data;
|
||
}
|
||
#endif
|
||
|
||
uint32_t NeoPoolGetFiltrationSpeed()
|
||
{
|
||
switch((NeoPoolGetData(MBF_RELAY_STATE) & MBMSK_RELAY_FILTSPEED) >> MBSHFT_RELAY_FILTSPEED) {
|
||
case 1:
|
||
return 1;
|
||
case 2:
|
||
return 2;
|
||
case 4:
|
||
return 3;
|
||
}
|
||
switch((NeoPoolGetData(MBF_PAR_FILTRATION_CONF) & MBMSK_PAR_FILTRATION_CONF_DEF_SPEED) >> MBSHFT_PAR_FILTRATION_CONF_DEF_SPEED) {
|
||
case MBV_PAR_FILTRATION_SPEED_SLOW:
|
||
return 1;
|
||
case MBV_PAR_FILTRATION_SPEED_MEDIUM:
|
||
return 2;
|
||
case MBV_PAR_FILTRATION_SPEED_FAST:
|
||
return 3;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
bool NeoPoolIsHydrolysis(void)
|
||
{
|
||
return (((NeoPoolGetData(MBF_PAR_MODEL) & MBMSK_MODEL_HIDRO)) ||
|
||
(NeoPoolGetData(MBF_HIDRO_STATUS) & (MBMSK_HIDRO_STATUS_CTRL_ACTIVE | MBMSK_HIDRO_STATUS_CTRL_ACTIVE)));
|
||
}
|
||
|
||
bool NeoPoolIsHydrolysisInPercent(void)
|
||
{
|
||
// determine type of units are used to display the hydrolysis/electrolysis:
|
||
// 1. If MBMSK_VS_FORCE_UNITS_PERCENTAGE bit of MBF_PAR_UICFG_MACH_VISUAL_STYLE register is set, "%" is displayed
|
||
if (NeoPoolGetData(MBF_PAR_UICFG_MACH_VISUAL_STYLE) & MBMSK_VS_FORCE_UNITS_PERCENTAGE) {
|
||
return true;
|
||
}
|
||
// 2. If MBMSK_VS_FORCE_UNITS_GRH bit of MBF_PAR_UICFG_MACH_VISUAL_STYLE register is set, "gr/h" is displayed
|
||
if (NeoPoolGetData(MBF_PAR_UICFG_MACH_VISUAL_STYLE) & MBMSK_VS_FORCE_UNITS_GRH) {
|
||
return false;
|
||
}
|
||
// 3. If neither of the above two bits is set:
|
||
// a. If MBF_PAR_UICFG_MACHINE is MACH_HIDROLIFE or MACH_BIONET, then "gr/h" is displayed
|
||
if (NeoPoolGetData(MBF_PAR_UICFG_MACHINE) == MBV_PAR_MACH_HIDROLIFE || NeoPoolGetData(MBF_PAR_UICFG_MACHINE) == MBV_PAR_MACH_BIONET) {
|
||
return false;
|
||
}
|
||
// b. If MBF_PAR_UICFG_MACHINE is MACH_GENERIC and MBMSK_ELECTROLISIS bit of MBF_PAR_UICFG_MACH_VISUAL_STYLE is set, "gr/h" is displayed.
|
||
if (NeoPoolGetData(MBF_PAR_UICFG_MACHINE) == MBV_PAR_MACH_GENERIC && (NeoPoolGetData(MBF_PAR_UICFG_MACH_VISUAL_STYLE) & MBMSK_ELECTROLISIS)) {
|
||
return false;
|
||
}
|
||
// c. If none of the above cases, "%" is displayed.
|
||
return true;
|
||
}
|
||
|
||
bool NeoPoolIspHModule(void)
|
||
{
|
||
return (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_MEASURE_ACTIVE);
|
||
}
|
||
|
||
|
||
bool NeoPoolIsRedox(void)
|
||
{
|
||
return (NeoPoolGetData(MBF_RX_STATUS) & MBMSK_RX_STATUS_MEASURE_ACTIVE);
|
||
}
|
||
|
||
|
||
bool NeoPoolIsChlorine(void)
|
||
{
|
||
return (NeoPoolGetData(MBF_CL_STATUS) & MBMSK_CL_STATUS_MEASURE_ACTIVE);
|
||
}
|
||
|
||
|
||
bool NeoPoolIsConductivity(void)
|
||
{
|
||
return (NeoPoolGetData(MBF_CD_STATUS) & MBMSK_CD_STATUS_MEASURE_ACTIVE);
|
||
}
|
||
|
||
|
||
bool NeoPoolIsIonization(void)
|
||
{
|
||
return (NeoPoolGetData(MBF_PAR_MODEL) & MBMSK_MODEL_ION);
|
||
}
|
||
|
||
|
||
/****************************************************************************/
|
||
void NeoPoolAppendModules(void)
|
||
{
|
||
ResponseAppend_P(PSTR("\"" D_NEOPOOL_JSON_MODULES "\":"));
|
||
ResponseAppend_P(PSTR("{\"" D_JSON_PH "\":%d"), NeoPoolIspHModule());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_REDOX "\":%d"), NeoPoolIsRedox());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_HYDROLYSIS "\":%d"), NeoPoolIsHydrolysis());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CHLORINE "\":%d"), NeoPoolIsChlorine());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CONDUCTIVITY "\":%d"), NeoPoolIsConductivity());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_IONIZATION "\":%d"), NeoPoolIsIonization());
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
|
||
void NeoPoolShow(bool json)
|
||
{
|
||
char neopool_type[60];
|
||
char stemp[200];
|
||
float fvalue;
|
||
|
||
if (neopool_error) {
|
||
return;
|
||
}
|
||
|
||
GetTextIndexed(neopool_type, sizeof(neopool_type), NeoPoolGetData(MBF_PAR_UICFG_MACHINE), kNeoPoolMachineNames);
|
||
*stemp = 0;
|
||
|
||
if (json) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_NAME "\":{"));
|
||
|
||
// Time
|
||
ResponseAppend_P(PSTR("\"" D_JSON_TIME "\":\"%s\","), GetDT(NeoPoolGetDataLong(MBF_PAR_TIME_LOW)).c_str());
|
||
|
||
// Type
|
||
ResponseAppend_P(PSTR("\"" D_JSON_TYPE "\":\"%s\","), neopool_type);
|
||
|
||
// Modules
|
||
NeoPoolAppendModules();
|
||
|
||
// Temperature
|
||
if (NeoPoolGetData(MBF_PAR_TEMPERATURE_ACTIVE)) {
|
||
fvalue = ConvertTemp((float)NeoPoolGetData(MBF_MEASURE_TEMPERATURE)/10);
|
||
ResponseAppend_P(PSTR(",\"" D_TEMPERATURE "\":%*_f"), Settings->flag2.temperature_resolution, &fvalue);
|
||
}
|
||
|
||
// Voltage
|
||
{
|
||
float f5volt = (float)NeoPoolGetData(MBF_VOLT_5)/620.69;
|
||
float f12volt = (float)NeoPoolGetData(MBF_VOLT_12)/1000;
|
||
float f24_36volt = (float)NeoPoolGetData(MBF_VOLT_24_36)/1000;
|
||
float f420mA = (float)NeoPoolGetData(MBF_AMP_4_20_MICRO)/100;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_POWERUNIT "\":{"));
|
||
if (neopool_power_module_version ||
|
||
NEOPOOL_MODBUS_OK == NeoPoolReadRegister(MBF_POWER_MODULE_VERSION, &neopool_power_module_version, 1)) {
|
||
ResponseAppend_P(PSTR("\"" D_JSON_VERSION "\":\"V%d.%d\","),
|
||
(neopool_power_module_version >> 8),
|
||
neopool_power_module_version & 0xff
|
||
);
|
||
}
|
||
if (neopool_power_module_nodeid[0] ||
|
||
NEOPOOL_MODBUS_OK == NeoPoolReadRegister(MBF_POWER_MODULE_NODEID, neopool_power_module_nodeid, nitems(neopool_power_module_nodeid))) {
|
||
if (Settings->flag6.neopool_outputsensitive) {
|
||
ResponseAppend_P(PSTR("\"" D_NEOPOOL_JSON_NODE_ID "\":\"%04X %04X %04X %04X %04X %04X\","),
|
||
neopool_power_module_nodeid[0],
|
||
neopool_power_module_nodeid[1],
|
||
neopool_power_module_nodeid[2],
|
||
neopool_power_module_nodeid[3],
|
||
neopool_power_module_nodeid[4],
|
||
neopool_power_module_nodeid[5]
|
||
);
|
||
}
|
||
else {
|
||
ResponseAppend_P(PSTR("\"" D_NEOPOOL_JSON_NODE_ID "\":\"XXXX XXXX XXXX XXXX XXXX %04X\","),
|
||
neopool_power_module_nodeid[5]
|
||
);
|
||
}
|
||
}
|
||
ResponseAppend_P(PSTR("\"5V\":%*_f,\"12V\":%*_f,\"24-30V\":%*_f,\"4-20mA\":%*_f}"),
|
||
Settings->flag2.voltage_resolution, &f5volt,
|
||
Settings->flag2.voltage_resolution, &f12volt,
|
||
Settings->flag2.voltage_resolution, &f24_36volt,
|
||
Settings->flag2.current_resolution, &f420mA);
|
||
}
|
||
|
||
// pH
|
||
if (NeoPoolIspHModule()) {
|
||
fvalue = (float)NeoPoolGetData(MBF_MEASURE_PH)/100;
|
||
ResponseAppend_P(PSTR(",\"" D_PH "\":{\"" D_JSON_DATA "\":" NEOPOOL_FMT_PH), NeoPoolSettings.flags.ph, &fvalue);
|
||
|
||
// S1
|
||
float fphmin = (float)NeoPoolGetData(MBF_PAR_PH2)/100;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_MIN "\":" NEOPOOL_FMT_PH), NeoPoolSettings.flags.ph, &fphmin);
|
||
float fphmax = (float)NeoPoolGetData(MBF_PAR_PH1)/100;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_MAX "\":" NEOPOOL_FMT_PH), NeoPoolSettings.flags.ph, &fphmax);
|
||
|
||
// S2
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_STATE "\":%d"), (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM));
|
||
|
||
// S3
|
||
int phpump = 0;
|
||
if (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_CTRL_ACTIVE) {
|
||
if (NeoPoolGetData(MBF_PH_STATUS) & (MBMSK_PH_STATUS_ACID_PUMP_ACTIVE | MBMSK_PH_STATUS_BASE_PUMP_ACTIVE)) {
|
||
phpump = 1;
|
||
} else {
|
||
phpump = 2;
|
||
}
|
||
}
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_PHPUMP "\":%d"), phpump);
|
||
|
||
// S4
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_FLOW1 "\":%d"), (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_CTRL_BY_FL) ? 0 : 1);
|
||
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_TANK "\":%d"), (MBV_PH_ACID_BASE_ALARM6 == (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM)) ? 0 : 1);
|
||
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
// Redox
|
||
if (NeoPoolIsRedox()) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_REDOX "\":{"));
|
||
ResponseAppend_P(PSTR("\"" D_JSON_DATA "\":" NEOPOOL_FMT_RX), NeoPoolGetData(MBF_MEASURE_RX));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SETPOINT "\":" NEOPOOL_FMT_RX), NeoPoolGetData(MBF_PAR_RX1));
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
// Chlorine
|
||
if (NeoPoolIsChlorine()) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CHLORINE "\":{"));
|
||
fvalue = (float)NeoPoolGetData(MBF_MEASURE_CL)/100;
|
||
ResponseAppend_P(PSTR("\"" D_JSON_DATA "\":" NEOPOOL_FMT_CL), NeoPoolSettings.flags.cl, &fvalue);
|
||
fvalue = (float)NeoPoolGetData(MBF_PAR_CL1)/100;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SETPOINT "\":" NEOPOOL_FMT_CL), NeoPoolSettings.flags.cl, &fvalue);
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
// Conductivity
|
||
if (NeoPoolIsConductivity()) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_CONDUCTIVITY "\":" NEOPOOL_FMT_CD), NeoPoolGetData(MBF_MEASURE_CONDUCTIVITY));
|
||
}
|
||
|
||
// Ionization
|
||
if (NeoPoolIsIonization()) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_IONIZATION "\":{"));
|
||
fvalue = (float)NeoPoolGetData(MBF_ION_CURRENT);
|
||
ResponseAppend_P(PSTR("\"" D_JSON_DATA "\":" NEOPOOL_FMT_ION), NeoPoolSettings.flags.ion, &fvalue);
|
||
fvalue = (float)NeoPoolGetData(MBF_PAR_ION);
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SETPOINT "\":" NEOPOOL_FMT_ION), NeoPoolSettings.flags.ion, &fvalue);
|
||
fvalue = (float)NeoPoolGetData(MBF_PAR_ION_NOM);
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_MAX "\":" NEOPOOL_FMT_ION), NeoPoolSettings.flags.ion, &fvalue);
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
// Hydrolysis
|
||
if (NeoPoolIsHydrolysis()) {
|
||
int decimals = NeoPoolIsHydrolysisInPercent() ? 0 : 1;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_HYDROLYSIS "\":{"));
|
||
uint16_t data = NeoPoolGetData(MBF_HIDRO_CURRENT);
|
||
fvalue = (float)data / 10;
|
||
ResponseAppend_P(PSTR( "\"" D_JSON_DATA "\":" NEOPOOL_FMT_HIDRO), decimals, &fvalue);
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_UNIT "\":\"%s\""), NeoPoolIsHydrolysisInPercent() ? PSTR(D_NEOPOOL_UNIT_PERCENT) : PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
uint16_t setpoint = NeoPoolGetData(MBF_PAR_HIDRO);
|
||
fvalue = (float)setpoint / 10;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SETPOINT "\":" NEOPOOL_FMT_HIDRO), decimals, &fvalue);
|
||
uint16_t max = NeoPoolGetData(MBF_PAR_HIDRO_NOM);
|
||
fvalue = (float)max / 10;
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_MAX "\":" NEOPOOL_FMT_HIDRO), decimals, &fvalue);
|
||
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_PERCENT "\":{"));
|
||
ResponseAppend_P(PSTR( "\"" D_JSON_DATA "\":%d"), data * 100 / max);
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SETPOINT "\":%d"), setpoint * 100 / max);
|
||
ResponseJsonEnd();
|
||
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CELL_RUNTIME "\":{"));
|
||
ResponseAppend_P(PSTR( "\"" D_NEOPOOL_JSON_CELL_RUNTIME_TOTAL "\":\"%s\""), GetDuration(NeoPoolGetDataLong(MBF_CELL_RUNTIME_LOW)).c_str());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CELL_RUNTIME_PART "\":\"%s\""), GetDuration(NeoPoolGetDataLong(MBF_CELL_RUNTIME_PART_LOW)).c_str());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CELL_RUNTIME_POL1 "\":\"%s\""), GetDuration(NeoPoolGetDataLong(MBF_CELL_RUNTIME_POLA_LOW)).c_str());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CELL_RUNTIME_POL2 "\":\"%s\""), GetDuration(NeoPoolGetDataLong(MBF_CELL_RUNTIME_POLB_LOW)).c_str());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CELL_RUNTIME_CHANGES "\":%ld"), NeoPoolGetDataLong(MBF_CELL_RUNTIME_POL_CHANGES_LOW));
|
||
ResponseJsonEnd();
|
||
|
||
// S1
|
||
const char *state = PSTR("");
|
||
if (0 == (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_MODULE_ACTIVE)) {
|
||
state = PSTR(D_NEOPOOL_STATUS_OFF);
|
||
} else if (0 == (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_FL1)) {
|
||
state = PSTR(D_NEOPOOL_STATUS_FLOW);
|
||
} else if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_POL1) {
|
||
state = PSTR(D_NEOPOOL_POLARIZATION "1");
|
||
} else if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_POL2) {
|
||
state = PSTR(D_NEOPOOL_POLARIZATION "2");
|
||
} else {
|
||
state = PSTR(D_NEOPOOL_STATUS_OFF);
|
||
}
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_STATE "\":\"%s\""), state);
|
||
// S2
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_COVER "\":%d"), (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_COVER) ? 1 : 0 );
|
||
// S3
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_SHOCK "\":%d"), (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_SHOCK_ENABLED) ? ((NeoPoolGetData(MBF_CELL_BOOST) & MBMSK_CELL_BOOST_NO_REDOX_CTL) ? 1 : 2) : 0 );
|
||
// S4
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_LOW "\":%d"), (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_LOW) ? 1 : 0 );
|
||
// S5
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_FLOW1 "\":%d"), (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_FL1) ? 0 : 1);
|
||
// S6
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_REDOX "\":%d"), (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_REDOX_ENABLED) ? 1 : 0);
|
||
|
||
ResponseJsonEnd();
|
||
}
|
||
|
||
// Filtration
|
||
if (0 != NeoPoolGetData(MBF_PAR_FILT_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_FILTRATION "\":"));
|
||
ResponseAppend_P(PSTR("{\"" D_NEOPOOL_JSON_STATE "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_FILT_GPIO)-1)) & 1);
|
||
uint16_t speed = NeoPoolGetFiltrationSpeed();
|
||
if (speed) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_FILTRATION_SPEED "\":%d"), (speed < 3) ? speed : 3);
|
||
}
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_FILTRATION_MODE "\":%d}"), NeoPoolGetData(MBF_PAR_FILT_MODE));
|
||
}
|
||
|
||
// Light
|
||
if (0 != NeoPoolGetData(MBF_PAR_LIGHTING_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_JSON_LIGHT "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_LIGHTING_GPIO)-1)) & 1);
|
||
}
|
||
|
||
// Relays
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY "\":{\"" D_NEOPOOL_JSON_STATE "\":["));
|
||
for(uint16_t i = 0; i < NEOPOOL_RELAY_MAX; i++) {
|
||
ResponseAppend_P(PSTR("%s%d"), i ? PSTR(",") : PSTR(""), (NeoPoolGetData(MBF_RELAY_STATE) >> i) & 1);
|
||
}
|
||
ResponseAppend_P(PSTR("]"));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_AUX "\":["));
|
||
for(uint16_t i = 3; i < NEOPOOL_RELAY_MAX; i++) {
|
||
ResponseAppend_P(PSTR("%s%d"), i > 3 ? PSTR(",") : PSTR(""), (NeoPoolGetData(MBF_RELAY_STATE) >> i) & 1);
|
||
}
|
||
ResponseAppend_P(PSTR("]"));
|
||
if (0 != NeoPoolGetData(MBF_PAR_PH_ACID_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_PH_ACID "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_PH_ACID_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_PH_BASE_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_PH_BASE "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_PH_BASE_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_RX_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_RX "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_RX_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_CL_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_CL "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_CL_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_CD_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_CD "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_CD_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_HEATING_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_HEATING "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_HEATING_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_UV_RELAY_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_UV "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_UV_RELAY_GPIO)-1)) & 1);
|
||
}
|
||
if (0 != NeoPoolGetData(MBF_PAR_FILTVALVE_GPIO)) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_FILTVALVE "\":%d"), (NeoPoolGetData(MBF_RELAY_STATE) >> (NeoPoolGetData(MBF_PAR_FILTVALVE_GPIO)-1)) & 1);
|
||
}
|
||
ResponseJsonEnd();
|
||
|
||
#ifdef NEOPOOL_CONNSTAT
|
||
if (NeoPoolSettings.flags.conn_stat) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CONNSTAT "\":{"));
|
||
ResponseAppend_P(PSTR( "\"" D_JSON_TIME "\":\"%s\""), GetDT(NeoPoolStats.time).c_str());
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CONNSTAT_MB_REQUESTS "\":%d"), NeoPoolStats.mb_requests);
|
||
uint32_t mb_sum = 0;
|
||
for(uint16_t i = 0; i < nitems(NeoPoolStats.mb_results); i++) {
|
||
char mbresult[32];
|
||
GetTextIndexed(mbresult, sizeof(mbresult), i, kNeoPoolMBResults);
|
||
ResponseAppend_P(PSTR(",\"%s\":%d"), mbresult,NeoPoolStats.mb_results[i]);
|
||
mb_sum += NeoPoolStats.mb_results[i];
|
||
}
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CONNSTAT_MB_RESULTS "\":%d"), NeoPoolStats.mb_requests - mb_sum);
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_CONNSTAT_DATA_OOR "\":%d"), NeoPoolStats.value_out_of_range);
|
||
ResponseJsonEnd();
|
||
}
|
||
#endif
|
||
|
||
ResponseJsonEnd();
|
||
|
||
#ifdef USE_WEBSERVER
|
||
} else {
|
||
char bg_color[10];
|
||
snprintf_P(bg_color, sizeof(bg_color), PSTR("#%02x%02x%02x"),
|
||
Settings->web_color[COL_BACKGROUND][0], // R
|
||
Settings->web_color[COL_BACKGROUND][1], // G
|
||
Settings->web_color[COL_BACKGROUND][2] // B
|
||
);
|
||
|
||
// Time
|
||
String nptime = GetDT(NeoPoolGetDataLong(MBF_PAR_TIME_LOW));
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_TIME, neopool_type, nptime.substring(0, nptime.length()-3).c_str());
|
||
|
||
// Temperature
|
||
if (NeoPoolGetData(MBF_PAR_TEMPERATURE_ACTIVE)) {
|
||
fvalue = ConvertTemp((float)NeoPoolGetData(MBF_MEASURE_TEMPERATURE)/10);
|
||
WSContentSend_PD(HTTP_SNS_F_TEMP, neopool_type, Settings->flag2.temperature_resolution, &fvalue, TempUnit());
|
||
}
|
||
|
||
// Voltage
|
||
{
|
||
float f5volt = (float)NeoPoolGetData(MBF_VOLT_5)/620.69;
|
||
float f12volt = (float)NeoPoolGetData(MBF_VOLT_12)/1000;
|
||
float f24_36volt = (float)NeoPoolGetData(MBF_VOLT_24_36)/1000;
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_VOLTAGE, neopool_type,
|
||
Settings->flag2.voltage_resolution, &f5volt,
|
||
Settings->flag2.voltage_resolution, &f12volt,
|
||
Settings->flag2.voltage_resolution, &f24_36volt);
|
||
}
|
||
|
||
// Hydrolysis
|
||
if (NeoPoolIsHydrolysis()) {
|
||
// Data
|
||
int decimals = NeoPoolIsHydrolysisInPercent() ? 0 : 1;
|
||
fvalue = (float)NeoPoolGetData(MBF_HIDRO_CURRENT)/10;
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_HYDROLYSIS, neopool_type, decimals, &fvalue,
|
||
NeoPoolIsHydrolysisInPercent() ? PSTR(D_NEOPOOL_UNIT_PERCENT) : PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
|
||
// S1
|
||
float fhidromax = (float)NeoPoolGetData(MBF_PAR_HIDRO)/10;
|
||
ext_snprintf_P(stemp, sizeof(stemp), PSTR(NEOPOOL_FMT_HIDRO " %s"), decimals, &fhidromax,
|
||
NeoPoolIsHydrolysisInPercent() ? PSTR(D_NEOPOOL_UNIT_PERCENT) : PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_INACTIVE, stemp);
|
||
WSContentSend_PD(PSTR(" "));
|
||
|
||
// S2
|
||
if (0 == (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_MODULE_ACTIVE)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_NORMAL, PSTR(D_NEOPOOL_STATUS_OFF));
|
||
} else if (0 == (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_FL1)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_STATUS_FLOW));
|
||
} else if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_POL1) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_NORMAL, PSTR(D_NEOPOOL_POLARIZATION "1"));
|
||
} else if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_POL2) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_NORMAL, PSTR(D_NEOPOOL_POLARIZATION "2"));
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_NORMAL, PSTR(D_NEOPOOL_STATUS_OFF));
|
||
}
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S3
|
||
if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_COVER) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_COVER));
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_DISABLED, PSTR(D_NEOPOOL_COVER));
|
||
}
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S4
|
||
if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_SHOCK_ENABLED) {
|
||
if ((NeoPoolGetData(MBF_CELL_BOOST) & MBMSK_CELL_BOOST_NO_REDOX_CTL) == 0) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_SHOCK "+" D_NEOPOOL_REDOX));
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_SHOCK));
|
||
}
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_DISABLED, PSTR(D_NEOPOOL_SHOCK));
|
||
}
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S5
|
||
if (NeoPoolGetData(MBF_HIDRO_STATUS) & MBMSK_HIDRO_STATUS_LOW) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_LOW));
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_DISABLED, PSTR(D_NEOPOOL_LOW));
|
||
}
|
||
|
||
WSContentSend_PD(PSTR("{e}"));
|
||
}
|
||
|
||
// pH
|
||
if (NeoPoolIspHModule()) {
|
||
// Data
|
||
fvalue = (float)NeoPoolGetData(MBF_MEASURE_PH)/100;
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_PH, neopool_type, NeoPoolSettings.flags.ph, &fvalue);
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S1
|
||
float fphmax = (float)NeoPoolGetData(MBF_PAR_PH1)/100;
|
||
ext_snprintf_P(stemp, sizeof(stemp), PSTR(NEOPOOL_FMT_PH), NeoPoolSettings.flags.ph, &fphmax);
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color,
|
||
(((uint16_t)(fvalue*10) > (uint16_t)(fphmax*10)) ? HTTP_SNS_NEOPOOL_STATUS_ACTIVE : HTTP_SNS_NEOPOOL_STATUS_INACTIVE), stemp);
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S2
|
||
if ((NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM) > 0) {
|
||
GetTextIndexed(stemp, sizeof(stemp), NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM, kNeoPoolpHAlarms);
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, stemp);
|
||
}
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S3
|
||
if (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_CTRL_ACTIVE) {
|
||
if (MBV_PH_ACID_BASE_ALARM6 == (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_STATUS_TANK));
|
||
} else if (NeoPoolGetData(MBF_PH_STATUS) & (MBMSK_PH_STATUS_ACID_PUMP_ACTIVE | MBMSK_PH_STATUS_BASE_PUMP_ACTIVE)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_STATUS_ON));
|
||
} else if (MBV_PH_ACID_BASE_ALARM0 != (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_ALARM)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_NORMAL, PSTR(D_NEOPOOL_STATUS_WAIT));
|
||
}
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_DISABLED, PSTR(D_NEOPOOL_STATUS_OFF));
|
||
}
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S4
|
||
if (0 == (NeoPoolGetData(MBF_PH_STATUS) & MBMSK_PH_STATUS_CTRL_BY_FL)) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_ACTIVE, PSTR(D_NEOPOOL_FLOW1));
|
||
} else {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color, HTTP_SNS_NEOPOOL_STATUS_DISABLED, PSTR(D_NEOPOOL_FLOW1));
|
||
}
|
||
WSContentSend_PD(PSTR("{e}"));
|
||
}
|
||
|
||
// Redox
|
||
// Status/Alarm: S1 S2
|
||
// S1: 0
|
||
// S2: FL1
|
||
if (NeoPoolIsRedox()) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_REDOX, neopool_type, NeoPoolGetData(MBF_MEASURE_RX));
|
||
WSContentSend_PD(PSTR(" "));
|
||
// S1
|
||
ext_snprintf_P(stemp, sizeof(stemp), PSTR(NEOPOOL_FMT_RX " " D_UNIT_MILLIVOLT), NeoPoolGetData(MBF_PAR_RX1));
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_STATUS, bg_color,
|
||
(NeoPoolGetData(MBF_HIDRO_CURRENT) ? HTTP_SNS_NEOPOOL_STATUS_ACTIVE : HTTP_SNS_NEOPOOL_STATUS_INACTIVE),
|
||
stemp);
|
||
WSContentSend_PD(PSTR("{e}"));
|
||
}
|
||
|
||
// Chlorine
|
||
if (NeoPoolIsChlorine()) {
|
||
fvalue = (float)NeoPoolGetData(MBF_MEASURE_CL)/100;
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_PPM_CHLORINE, neopool_type, NeoPoolSettings.flags.ph, &fvalue);
|
||
}
|
||
|
||
// Conductivity
|
||
if (NeoPoolIsConductivity()) {
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_CONDUCTIVITY, neopool_type, NeoPoolGetData(MBF_MEASURE_CONDUCTIVITY));
|
||
}
|
||
|
||
// Ionization
|
||
if (NeoPoolIsIonization()) {
|
||
char spol[100];
|
||
snprintf_P(spol, sizeof(spol), PSTR(" " D_NEOPOOL_POLARIZATION "%d"), (NeoPoolGetData(MBF_ION_STATUS) & (MBMSK_ION_STATUS_POL1 | MBMSK_ION_STATUS_POL2)) >> 13);
|
||
snprintf_P(stemp, sizeof(stemp), PSTR("%s%s%s"),
|
||
(NeoPoolGetData(MBF_ION_STATUS) & (MBMSK_ION_STATUS_POL1 | MBMSK_ION_STATUS_POL2)) ? spol : PSTR(""),
|
||
NeoPoolGetData(MBF_ION_STATUS) & MBMSK_ION_STATUS_ON_TARGET ? PSTR(" " D_NEOPOOL_SETPOINT_OK) : PSTR(""),
|
||
NeoPoolGetData(MBF_ION_STATUS) & MBMSK_ION_STATUS_PROGTIME_EXCEEDED ? PSTR(" " D_NEOPOOL_PR_OFF) : PSTR("")
|
||
);
|
||
fvalue = (float)NeoPoolGetData(MBF_ION_CURRENT);
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_IONIZATION, neopool_type,
|
||
NeoPoolSettings.flags.ion, &fvalue,
|
||
stemp,
|
||
NeoPoolGetData(MBF_ION_STATUS) & MBMSK_ION_STATUS_LOW ? PSTR(" " D_NEOPOOL_LOW) : PSTR("")
|
||
);
|
||
}
|
||
|
||
// Filtration mode
|
||
GetTextIndexed(stemp, sizeof(stemp), NeoPoolGetData(MBF_PAR_FILT_MODE) <= MBV_PAR_FILT_INTELLIGENT ? NeoPoolGetData(MBF_PAR_FILT_MODE) : nitems(kNeoPoolFiltrationMode)-1, kNeoPoolFiltrationMode);
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_FILT_MODE, neopool_type, stemp);
|
||
|
||
// Relays
|
||
for (uint32_t i = 0; i < NEOPOOL_RELAY_MAX; i++) {
|
||
char sdesc[24];
|
||
memset(sdesc, 0, nitems(sdesc));
|
||
memset(stemp, 0, nitems(stemp));
|
||
if (0 != NeoPoolGetData(MBF_PAR_PH_ACID_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_PH_ACID_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_PH_ACID), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_PH_BASE_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_PH_BASE_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_PH_BASE), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_RX_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_RX_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_RX), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_CL_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_CL_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_CL), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_CD_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_CD_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_CD), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_FILT_GPIO) && i == NeoPoolGetData(MBF_PAR_FILT_GPIO)-1) {
|
||
char smotorspeed[32];
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_FILTRATION), sizeof(sdesc));
|
||
GetTextIndexed(smotorspeed, sizeof(smotorspeed), NeoPoolGetFiltrationSpeed(), kNeoPoolFiltrationSpeed);
|
||
snprintf_P(stemp, sizeof(stemp), PSTR("%s%s%s%s"), ((NeoPoolGetData(MBF_RELAY_STATE) & (1<<i))?D_ON:D_OFF), *smotorspeed ? PSTR(" (") : PSTR(""), smotorspeed, *smotorspeed ? PSTR(")") : PSTR(""));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_LIGHTING_GPIO) && i == NeoPoolGetData(MBF_PAR_LIGHTING_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_LIGHT), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_HEATING_GPIO) && i == NeoPoolGetData(MBF_PAR_HEATING_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_HEATING), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_UV_RELAY_GPIO) && i == NeoPoolGetData(MBF_PAR_UV_RELAY_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_UV), sizeof(sdesc));
|
||
} else if (0 != NeoPoolGetData(MBF_PAR_FILTVALVE_GPIO) && i == NeoPoolGetData(MBF_PAR_FILTVALVE_GPIO)-1) {
|
||
strncpy_P(sdesc, PSTR(D_NEOPOOL_RELAY_VALVE), sizeof(sdesc));
|
||
} else if (i > 2) {
|
||
// Aux
|
||
char sname[(MBF_PAR_UICFG_MACH_NAME_AUX2 - MBF_PAR_UICFG_MACH_NAME_AUX1) * 2 + 1];
|
||
uint16_t base = MBF_PAR_UICFG_MACH_NAME_AUX1 + ((i - 3) * (MBF_PAR_UICFG_MACH_NAME_AUX2 - MBF_PAR_UICFG_MACH_NAME_AUX1));
|
||
for (uint16_t k = 0; k < (MBF_PAR_UICFG_MACH_NAME_AUX2 - MBF_PAR_UICFG_MACH_NAME_AUX1); k++) {
|
||
uint16_t data = NeoPoolGetData(base + k);
|
||
sname[k*2] = (char)(data >> 8);
|
||
sname[k*2 + 1] = (char)(data & 0xFF);
|
||
}
|
||
if (*sname) {
|
||
snprintf_P(sdesc, sizeof(sdesc), PSTR(D_NEOPOOL_RELAY_AUX " %d (%s)"), i-2, sname);
|
||
} else {
|
||
snprintf_P(sdesc, sizeof(sdesc), PSTR(D_NEOPOOL_RELAY_AUX " %d"), i-2);
|
||
}
|
||
} else {
|
||
// unassigned relay
|
||
snprintf_P(sdesc, sizeof(sdesc), PSTR(D_NEOPOOL_RELAY " %d"), i+1);
|
||
}
|
||
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_RELAY, neopool_type, sdesc,
|
||
'\0' == *stemp ? ((NeoPoolGetData(MBF_RELAY_STATE) & (1<<i))?PSTR(D_ON):PSTR(D_OFF)) : stemp);
|
||
}
|
||
|
||
{
|
||
// Cell runtime
|
||
WSContentSend_PD(HTTP_SNS_NEOPOOL_CELL_RUNTIME, neopool_type,
|
||
GetDuration(NeoPoolGetDataLong(MBF_CELL_RUNTIME_LOW)).c_str());
|
||
}
|
||
|
||
#endif // USE_WEBSERVER
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/****************************************************************************\
|
||
* Command implementation
|
||
\****************************************************************************/
|
||
|
||
void NeopoolReadWriteResponse(uint16_t addr, uint16_t *data, uint16_t cnt, bool fbits32, int16_t bit)
|
||
{
|
||
const char *data_fmt;
|
||
uint32_t ldata;
|
||
|
||
Response_P(PSTR("{\"%s\":{\"" D_JSON_ADDRESS "\":"), XdrvMailbox.command);
|
||
ResponseAppend_P(NEOPOOL_RESULT_HEX == NeoPoolSettings.result ? PSTR("\"0x%04X\"") : PSTR("%d"), addr);
|
||
ResponseAppend_P(PSTR(",\"" D_JSON_DATA "\":"));
|
||
|
||
data_fmt = PSTR("%ld");
|
||
if (NEOPOOL_RESULT_HEX == NeoPoolSettings.result) {
|
||
data_fmt = fbits32 ? PSTR("\"0x%08X\"") : PSTR("\"0x%04X\"");
|
||
}
|
||
ldata = (uint32_t)data[0];
|
||
if (fbits32) {
|
||
ldata |= (uint32_t)data[1] << 16;
|
||
}
|
||
if ( cnt > 1 ) {
|
||
char sdel[2] = {0};
|
||
ResponseAppend_P(PSTR("["));
|
||
for(uint16_t i=0; i<cnt; i++) {
|
||
ResponseAppend_P(PSTR("%s"), sdel);
|
||
ldata = (uint32_t)data[(fbits32+1)*i];
|
||
if (fbits32) {
|
||
ldata |= (uint32_t)data[(fbits32+1)*i+1] << 16;
|
||
}
|
||
ResponseAppend_P(data_fmt, ldata);
|
||
*sdel = ',';
|
||
}
|
||
ResponseAppend_P(PSTR("]"));
|
||
} else {
|
||
ResponseAppend_P(data_fmt, ldata);
|
||
}
|
||
if (bit >= 0) {
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_BIT "%d\":%ld"), bit, (ldata>>bit) & 1);
|
||
}
|
||
ResponseJsonEndEnd();
|
||
}
|
||
|
||
|
||
void NeopoolCmndError(void)
|
||
{
|
||
Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_ERROR "\"}"));
|
||
}
|
||
|
||
|
||
void NeopoolResponseError(void)
|
||
{
|
||
ResponseCmndChar(PSTR(D_JSON_ERROR));
|
||
}
|
||
|
||
|
||
void CmndNeopoolResult(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload < NEOPOOL_RESULT_MAX) {
|
||
NeoPoolSettings.result = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(NeoPoolSettings.result);
|
||
}
|
||
|
||
|
||
void CmndNeopoolReadReg(void)
|
||
{
|
||
uint16_t addr, data[30] = { 0 }, cnt = 1;
|
||
uint32_t value[2] = { 0 };
|
||
uint32_t params_cnt = ParseParameters(nitems(value), value);
|
||
bool fbits32 = !strcasecmp_P(XdrvMailbox.command, PSTR(D_PRFX_NEOPOOL D_CMND_NP_READL));
|
||
|
||
cnt = 1;
|
||
if (2 == params_cnt) {
|
||
cnt = value[1];
|
||
}
|
||
if (params_cnt && cnt < (fbits32 ? (nitems(data)/2) : nitems(data))) {
|
||
addr = value[0];
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(addr, data, fbits32 ? (cnt*2) : cnt)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
}
|
||
NeopoolReadWriteResponse(addr, data, cnt, fbits32, -1);
|
||
}
|
||
|
||
|
||
void CmndNeopoolWriteReg(void)
|
||
{
|
||
uint16_t addr, data[20] = { 0 }, cnt;
|
||
uint32_t value[(nitems(data)/2)+1] = { 0 };
|
||
uint32_t params_cnt = ParseParameters(nitems(value), value);
|
||
bool fbits32 = !strcasecmp_P(XdrvMailbox.command, PSTR(D_PRFX_NEOPOOL D_CMND_NP_WRITEL));
|
||
|
||
if (params_cnt > 1) {
|
||
addr = value[0];
|
||
cnt = params_cnt-1;
|
||
for (uint32_t i = 0; i < cnt; i++) {
|
||
if (fbits32) {
|
||
data[i*2] = value[i+1]; // LSB
|
||
data[i*2+1] = value[i+1]>>16; // MSB
|
||
} else {
|
||
data[i] = value[i+1];
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegister(addr, data, fbits32 ? cnt*2 : cnt)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(addr, data, fbits32 ? (cnt*2) : cnt)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
NeopoolReadWriteResponse(addr, data, cnt, fbits32, -1);
|
||
}
|
||
|
||
|
||
void CmndNeopoolBit(void)
|
||
{
|
||
uint16_t addr, data;
|
||
uint16_t bit;
|
||
uint32_t value[3] = { 0 };
|
||
uint32_t params_cnt = ParseParameters(nitems(value), value);
|
||
bool fbits32 = !strcasecmp_P(XdrvMailbox.command, PSTR(D_PRFX_NEOPOOL D_CMND_NP_BITL));
|
||
uint16_t tempdata[2];
|
||
|
||
if (params_cnt >= 2) {
|
||
addr = value[0];
|
||
bit = (uint16_t)value[1];
|
||
|
||
if (bit >= 0 && bit < 16<<fbits32) {
|
||
if (3 == params_cnt) {
|
||
data = value[2];
|
||
if (data >=0 && data <= 1 && NEOPOOL_MODBUS_OK == NeoPoolReadRegister(addr, tempdata, 1<<fbits32)) {
|
||
if (fbits32) {
|
||
uint32_t tempdata32 = tempdata[0] | (tempdata[1]<<16);
|
||
tempdata32 &= ~(1<<bit);
|
||
tempdata32 |= data<<bit;
|
||
tempdata[0] = (uint16_t)tempdata32;
|
||
tempdata[1] = (uint16_t)(tempdata32>>16);
|
||
} else {
|
||
tempdata[0] &= ~(1<<bit);
|
||
tempdata[0] |= (data<<bit);
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegister(addr, tempdata, 1<<fbits32)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
} else {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(addr, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
NeopoolReadWriteResponse(addr, &data, 1, fbits32, bit);
|
||
return;
|
||
}
|
||
|
||
}
|
||
NeopoolCmndError();
|
||
}
|
||
|
||
|
||
void CmndNeopoolFiltrationRes(uint16_t data)
|
||
{
|
||
uint16_t speed = NeoPoolGetFiltrationSpeed();
|
||
if (speed) {
|
||
Response_P(PSTR("{\"%s\":\"%s\",\"" D_NEOPOOL_JSON_FILTRATION_SPEED "\":\"%d\"}"),
|
||
XdrvMailbox.command,
|
||
GetStateText(data),
|
||
(speed < 3) ? speed : 3);
|
||
} else {
|
||
ResponseCmndStateText(data);
|
||
}
|
||
}
|
||
|
||
void CmndNeopoolFiltration(void)
|
||
{
|
||
uint16_t addr = MBF_PAR_FILT_MANUAL_STATE;
|
||
uint16_t data;
|
||
uint16_t filtration_conf;
|
||
uint32_t value[2] = { 0 };
|
||
uint32_t params_cnt = ParseParameters(nitems(value), value);
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_FILTRATION_CONF, &filtration_conf, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (params_cnt > 2 || (params_cnt > 1 && (MBV_PAR_FILTRATION_TYPE_STANDARD == (filtration_conf & MBMSK_PAR_FILTRATION_CONF_TYPE)))) {
|
||
// no speed control for standard filtration types
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
if (params_cnt > 1) {
|
||
if (value[1] >= 1 && value[1] <= 3) {
|
||
// Set filtration speed first
|
||
NeoPoolWriteRegisterWord(MBF_PAR_FILTRATION_CONF,
|
||
(filtration_conf & ~MBMSK_PAR_FILTRATION_CONF_DEF_SPEED) | ((value[1] - 1) << MBSHFT_PAR_FILTRATION_CONF_DEF_SPEED));
|
||
NeoPoolWriteRegisterWord(MBF_EXEC, 1);
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
}
|
||
if (value[0] >= 0 && value[0] <= 2) {
|
||
// Set MBF_PAR_FILT_MODE
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_PAR_FILT_MODE, MBV_PAR_FILT_MANUAL)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (2 == value[0]) {
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_FILTRATION_STATE, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
value[0] = data ? 0 : 1;
|
||
}
|
||
// Set filtration mode to manual
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_PAR_FILT_MANUAL_STATE, value[0])) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
CmndNeopoolFiltrationRes(value[0]);
|
||
return;
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_FILTRATION_STATE, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
CmndNeopoolFiltrationRes(data);
|
||
}
|
||
|
||
|
||
void CmndNeopoolFiltrationMode(void)
|
||
{
|
||
uint16_t addr = MBF_PAR_FILT_MODE;
|
||
uint16_t data;
|
||
char stemp[80];
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
char command[CMDSZ];
|
||
int mode = GetCommandCode(command, sizeof(command), XdrvMailbox.data, kNeoPoolFiltrationModeCmnd);
|
||
if (mode >= 0) {
|
||
XdrvMailbox.payload = pgm_read_byte(sNeoPoolFiltrationMode + mode);
|
||
}
|
||
if ((XdrvMailbox.payload >= MBV_PAR_FILT_MANUAL && XdrvMailbox.payload <= MBV_PAR_FILT_INTELLIGENT) ||
|
||
MBV_PAR_FILT_BACKWASH == XdrvMailbox.payload) {
|
||
// Set MBF_PAR_FILT_MODE
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(addr, XdrvMailbox.payload)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(addr, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
ResponseCmndChar(GetTextIndexed(stemp, sizeof(stemp), data < MBV_PAR_FILT_INTELLIGENT ? data : nitems(kNeoPoolFiltrationModeCmnd)-1, kNeoPoolFiltrationMode));
|
||
}
|
||
|
||
|
||
void CmndNeopoolFiltrationSpeed(void)
|
||
{
|
||
uint16_t speed;
|
||
uint16_t filtration_conf;
|
||
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_FILTRATION_CONF, &filtration_conf, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (MBV_PAR_FILTRATION_TYPE_STANDARD == (filtration_conf & MBMSK_PAR_FILTRATION_CONF_TYPE)) {
|
||
// no speed control for standard filtration types
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
|
||
speed = NeoPoolGetFiltrationSpeed();
|
||
if (XdrvMailbox.data_len) {
|
||
if (XdrvMailbox.payload >= 1 && XdrvMailbox.payload <= 3) {
|
||
speed = XdrvMailbox.payload;
|
||
// Set filtration speed
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_PAR_FILTRATION_CONF,
|
||
(filtration_conf & ~MBMSK_PAR_FILTRATION_CONF_DEF_SPEED) | ((speed - 1) << MBSHFT_PAR_FILTRATION_CONF_DEF_SPEED))) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
NeoPoolWriteRegisterWord(MBF_EXEC, 1);
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
}
|
||
ResponseCmndNumber(speed);
|
||
}
|
||
|
||
|
||
void CmndNeopoolBoost(void)
|
||
{
|
||
uint16_t data;
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
char command[CMDSZ];
|
||
int mode = GetCommandCode(command, sizeof(command), XdrvMailbox.data, kNeoPoolBoostCmnd);
|
||
if (mode < 0) {
|
||
mode = XdrvMailbox.payload;
|
||
}
|
||
if (mode >= 0 && mode < nitems(sNeoPoolBoost)) {
|
||
uint16_t boostflags = pgm_read_word(sNeoPoolBoost + mode);
|
||
if (CmndNeopoolSetParam(MBF_CELL_BOOST, boostflags, 1, 0, (float)0xFFFF)) {
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_NOTIFICATION, 0x7F)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_CELL_BOOST, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
for(uint16_t i=0; i < nitems(kNeoPoolBoostCmnd); i++) {
|
||
if (data == pgm_read_word(sNeoPoolBoost + i)) {
|
||
char stemp[80];
|
||
ResponseCmndChar(GetTextIndexed(stemp, sizeof(stemp), i, kNeoPoolBoostCmnd));
|
||
return;
|
||
}
|
||
}
|
||
NeopoolCmndError();
|
||
}
|
||
|
||
|
||
void CmndNeopoolTime(void)
|
||
{
|
||
uint16_t data[2];
|
||
uint32_t np_time;
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
np_time = XdrvMailbox.payload;
|
||
if (0 == XdrvMailbox.payload) {
|
||
np_time = Rtc.local_time;
|
||
}
|
||
if (1 == XdrvMailbox.payload) {
|
||
np_time = Rtc.utc_time;
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: set time to %ld"), np_time);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
data[0] = np_time;
|
||
data[1] = np_time>>16;
|
||
NeoPoolWriteRegister(MBF_PAR_TIME_LOW, data, 2);
|
||
NeoPoolWriteRegisterWord(MBF_ACTION_COPY_TO_RTC, 1);
|
||
}
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_TIME_LOW, data, 2)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
np_time = (uint32_t)data[0] | ((uint32_t)data[1] << 16);
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: time read %ld"), np_time);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
ResponseCmndChar(GetDT(NeoPoolGetDataLong(MBF_PAR_TIME_LOW)).c_str());
|
||
}
|
||
|
||
|
||
void CmndNeopoolLight(void)
|
||
{
|
||
uint16_t data, set;
|
||
uint16_t timer_val[] = {MBV_PAR_CTIMER_ALWAYS_OFF, MBV_PAR_CTIMER_ALWAYS_ON, POWER_TOGGLE, MBV_PAR_CTIMER_ENABLED};
|
||
uint32_t value[2] = { 0 };
|
||
uint32_t params_cnt = ParseParameters(nitems(value), value);
|
||
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_LIGHTING_GPIO, &neopool_light_relay, 1) ||
|
||
NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_RELAY_STATE, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (neopool_light_relay >= 1 && neopool_light_relay <= NEOPOOL_RELAY_MAX) {
|
||
// get/set light
|
||
if (1 == params_cnt && XdrvMailbox.payload >= 0 && XdrvMailbox.payload < nitems(timer_val)) {
|
||
if (POWER_TOGGLE == timer_val[XdrvMailbox.payload]) {
|
||
XdrvMailbox.payload = ((data >>= (neopool_light_relay - 1)) & 1) ? POWER_OFF : POWER_ON;
|
||
}
|
||
NeoPoolWriteRegisterWord((uint16_t)MBF_PAR_TIMER_BLOCK_LIGHT_INT + (uint16_t)MBV_TIMER_OFFMB_TIMER_ENABLE, timer_val[XdrvMailbox.payload]);
|
||
NeoPoolWriteRegisterWord(MBF_EXEC, 1);
|
||
// data >>= (neopool_light_relay - 1);
|
||
ResponseCmndStateText(XdrvMailbox.payload);
|
||
return;
|
||
}
|
||
// set next light program
|
||
if (nitems(timer_val) == XdrvMailbox.payload) {
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_SET_MANUAL_CTRL, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (params_cnt > 1) {
|
||
if (value[1] >= NEOPOOL_LIGHT_PRG_DELAY_MIN and value[1] <= NEOPOOL_LIGHT_PRG_DELAY_MAX) {
|
||
// use given delay
|
||
neopool_light_prg_delay = value[1];
|
||
} else {
|
||
NeopoolCmndError();
|
||
return;
|
||
}
|
||
} else {
|
||
// use default delay
|
||
neopool_light_prg_delay = NEOPOOL_LIGHT_PRG_DELAY;
|
||
}
|
||
if (data & 1<<(neopool_light_relay-1)) {
|
||
// light already on, start programming immediately
|
||
CmndNeopoolLightPrgWaitStart();
|
||
} else {
|
||
// light currently off: must first switched on and wait for inital prg delay NEOPOOL_LIGHT_PRG_WAIT
|
||
data |= 1<<(neopool_light_relay-1);
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegister(MBF_RELAY_STATE, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
neopoll_cmd = &CmndNeopoolLightPrgWaitStart;
|
||
neopoll_cmd_delay = NEOPOOL_LIGHT_PRG_WAIT * 4 / 10;
|
||
}
|
||
ResponseCmndDone();
|
||
return;
|
||
}
|
||
if (0 == params_cnt) {
|
||
ResponseCmndStateText((data >>= (neopool_light_relay - 1)) & 1);
|
||
return;
|
||
}
|
||
|
||
}
|
||
NeopoolCmndError();
|
||
}
|
||
|
||
|
||
void CmndNeopoolLightPrgWaitStart(void)
|
||
{
|
||
uint16_t data;
|
||
|
||
// start prg sequence with light off
|
||
if (NEOPOOL_MODBUS_OK == NeoPoolReadRegister(MBF_RELAY_STATE, &data, 1)) {
|
||
if (NEOPOOL_MODBUS_OK == NeoPoolWriteRegisterWord(MBF_RELAY_STATE, data & ~(1<<(neopool_light_relay-1)))) {
|
||
neopoll_cmd = &CmndNeopoolLightPrgEnd;
|
||
// then wait for given prg sequence delay
|
||
neopoll_cmd_delay = neopool_light_prg_delay * 4 / 10;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolLightPrgEnd(void)
|
||
{
|
||
// exit manual ctrl
|
||
NeoPoolWriteRegisterWord(MBF_SET_MANUAL_CTRL, 0);
|
||
// switch light on to finish prg sequence
|
||
NeoPoolWriteRegisterWord((uint16_t)MBF_PAR_TIMER_BLOCK_LIGHT_INT + (uint16_t)MBV_TIMER_OFFMB_TIMER_ENABLE, MBV_PAR_CTIMER_ALWAYS_ON);
|
||
NeoPoolWriteRegisterWord(MBF_EXEC, 1);
|
||
}
|
||
|
||
|
||
bool CmndNeopoolSetParam(uint16_t reg, uint16_t data, uint16_t factor, float min, float max)
|
||
{
|
||
if (data >= min*(float)factor && data <= max*(float)factor) {
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(reg, data) ||
|
||
NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_EXEC, 1) ||
|
||
NEOPOOL_MODBUS_OK != NeoPoolWriteRegisterWord(MBF_SAVE_TO_EEPROM, 1)) {
|
||
NeopoolResponseError();
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
} else {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
bool CmndNeopoolSetParam(uint16_t reg, uint16_t factor, float min, float max)
|
||
{
|
||
uint16_t data;
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
return CmndNeopoolSetParam(reg, (int)(CharToFloat(XdrvMailbox.data) * (float)factor), factor, min, max);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
void CmndNeopoolGetParam(uint16_t reg, uint16_t factor, uint16_t decimals, const char* unit)
|
||
{
|
||
uint16_t data;
|
||
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(reg, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
|
||
float fvalue = (float)(data) / (float)factor;
|
||
if (nullptr == unit) {
|
||
ResponseCmndFloat(fvalue, decimals);
|
||
}
|
||
else {
|
||
Response_P(PSTR("{\"%s\":%*_f,\"" D_NEOPOOL_JSON_UNIT "\":\"%s\"}"), XdrvMailbox.command, decimals, &fvalue, unit);
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolGetParam(uint16_t reg, uint16_t factor, uint16_t decimals)
|
||
{
|
||
CmndNeopoolGetParam(reg, factor, decimals, nullptr);
|
||
}
|
||
|
||
|
||
void CmndNeopoolpHMin(void)
|
||
{
|
||
if (NeoPoolIspHModule()) {
|
||
uint16_t data;
|
||
// read pH max
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_PH1, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (CmndNeopoolSetParam(MBF_PAR_PH2, 100, 0, (float)data/100)) {
|
||
CmndNeopoolGetParam(MBF_PAR_PH2, 100, NeoPoolSettings.flags.ph);
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolpHMax(void)
|
||
{
|
||
if (NeoPoolIspHModule()) {
|
||
uint16_t data;
|
||
// read pH min
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_PH2, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (CmndNeopoolSetParam(MBF_PAR_PH1, 100, (float)data/100, 14)) {
|
||
CmndNeopoolGetParam(MBF_PAR_PH1, 100, NeoPoolSettings.flags.ph);
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolRedox(void)
|
||
{
|
||
if (NeoPoolIsRedox()) {
|
||
if (CmndNeopoolSetParam(MBF_PAR_RX1, 1, 0, 1000)) {
|
||
CmndNeopoolGetParam(MBF_PAR_RX1, 1, 0);
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolHydrolysisSet(uint16_t max, const char* unit)
|
||
{
|
||
// set value in percent or g/h
|
||
if (CmndNeopoolSetParam(MBF_PAR_HIDRO, 10, 0, (float)max/10)) {
|
||
CmndNeopoolGetParam(MBF_PAR_HIDRO, 10, NeoPoolIsHydrolysisInPercent() ? 0 : 1, unit);
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolHydrolysis(void)
|
||
{
|
||
if (NeoPoolIsHydrolysis()) {
|
||
|
||
if (XdrvMailbox.data_len) {
|
||
|
||
uint16_t max;
|
||
// read hydrolysis maximum production level
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_HIDRO_NOM, &max, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - MBF_PAR_HIDRO_NOM = %d"), max);
|
||
|
||
// only set if param is given
|
||
if (NeoPoolIsHydrolysisInPercent()) {
|
||
|
||
// % system
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - %% system, always set value in %%"));
|
||
// always set value in %
|
||
CmndNeopoolHydrolysisSet(max, PSTR(D_NEOPOOL_UNIT_PERCENT));
|
||
|
||
} else {
|
||
|
||
// g/h system
|
||
TrimSpace(XdrvMailbox.data);
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - g/h system, param = '%s'"), XdrvMailbox.data);
|
||
bool ispercent = false;
|
||
if ('%' == *(XdrvMailbox.data + strlen(XdrvMailbox.data) - 1)) {
|
||
ispercent = true;
|
||
*(XdrvMailbox.data + strlen(XdrvMailbox.data) - 1) = '\0';
|
||
}
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - g/h system, param = '%s', using percent: %s"), XdrvMailbox.data, ispercent ? PSTR("true") : PSTR("false"));
|
||
if (*XdrvMailbox.data) {
|
||
|
||
// only set if param is given (without the possible %)
|
||
if (ispercent) {
|
||
// set value in precent based on max g/h
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - g/h system, set value in %% = %d"), max * TextToInt(XdrvMailbox.data) / 100);
|
||
// max * (float)% / 100
|
||
if (CmndNeopoolSetParam(MBF_PAR_HIDRO, (int)((float)max * CharToFloat(XdrvMailbox.data) / 100), 1, 0, max)) {
|
||
CmndNeopoolGetParam(MBF_PAR_HIDRO, 10, 1, PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
else {
|
||
// set value in g/h
|
||
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NPHydrolysis - g/h system, set value in g/h"));
|
||
CmndNeopoolHydrolysisSet(max, PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
}
|
||
|
||
} // XdrvMailbox.data
|
||
|
||
} // g/h system
|
||
|
||
} // XdrvMailbox.data_len
|
||
else {
|
||
CmndNeopoolGetParam(MBF_PAR_HIDRO, 10, 0, NeoPoolIsHydrolysisInPercent() ? PSTR(D_NEOPOOL_UNIT_PERCENT) : PSTR(D_NEOPOOL_UNIT_GPERH));
|
||
}
|
||
|
||
} // NeoPoolIsHydrolysis()
|
||
}
|
||
|
||
|
||
void CmndNeopoolIonization(void)
|
||
{
|
||
if (NeoPoolIsIonization()) {
|
||
uint16_t data;
|
||
// read ionization maximum production level
|
||
if (NEOPOOL_MODBUS_OK != NeoPoolReadRegister(MBF_PAR_ION_NOM, &data, 1)) {
|
||
NeopoolResponseError();
|
||
return;
|
||
}
|
||
if (CmndNeopoolSetParam(MBF_PAR_ION, 1, 0, (float)data)) {
|
||
CmndNeopoolGetParam(MBF_PAR_ION, 1, NeoPoolSettings.flags.ion);
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolChlorine(void)
|
||
{
|
||
if (NeoPoolIsChlorine()) {
|
||
if (CmndNeopoolSetParam(MBF_PAR_CL1, 100, 0, 10)) {
|
||
CmndNeopoolGetParam(MBF_PAR_CL1, 100, NeoPoolSettings.flags.cl);
|
||
}
|
||
} else {
|
||
NeopoolCmndError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolControl(void)
|
||
{
|
||
Response_P(PSTR("{"));
|
||
NeoPoolAppendModules();
|
||
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY "\":{"));
|
||
ResponseAppend_P(PSTR( "\"" D_NEOPOOL_JSON_RELAY_PH_ACID "\":%d"), NeoPoolGetData(MBF_PAR_PH_ACID_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_PH_BASE "\":%d"), NeoPoolGetData(MBF_PAR_PH_BASE_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_RX "\":%d"), NeoPoolGetData(MBF_PAR_RX_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_CL "\":%d"), NeoPoolGetData(MBF_PAR_CL_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_CD "\":%d"), NeoPoolGetData(MBF_PAR_CD_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_HEATING "\":%d"), NeoPoolGetData(MBF_PAR_HEATING_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_UV "\":%d"), NeoPoolGetData(MBF_PAR_UV_RELAY_GPIO));
|
||
ResponseAppend_P(PSTR(",\"" D_NEOPOOL_JSON_RELAY_FILTVALVE "\":%d"), NeoPoolGetData(MBF_PAR_FILTVALVE_GPIO));
|
||
ResponseJsonEndEnd();
|
||
}
|
||
|
||
|
||
void CmndNeopoolTelePeriod(void)
|
||
{
|
||
if (XdrvMailbox.data_len && ((XdrvMailbox.payload == 0 || (XdrvMailbox.payload >= 5 && XdrvMailbox.payload <= 3600)))) {
|
||
NeoPoolSettings.npteleperiod = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(NeoPoolSettings.npteleperiod);
|
||
}
|
||
|
||
|
||
void CmndNeopoolSave(void)
|
||
{
|
||
if (NEOPOOL_MODBUS_OK == NeoPoolWriteRegisterWord(MBF_SAVE_TO_EEPROM, 1)) {
|
||
ResponseCmndDone();
|
||
} else {
|
||
NeopoolResponseError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolExec(void)
|
||
{
|
||
if (NEOPOOL_MODBUS_OK == NeoPoolWriteRegisterWord(MBF_EXEC, 1)) {
|
||
ResponseCmndDone();
|
||
} else {
|
||
NeopoolResponseError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolEscape(void)
|
||
{
|
||
if (NEOPOOL_MODBUS_OK == NeoPoolWriteRegisterWord(MBF_ESCAPE, 1)) {
|
||
ResponseCmndDone();
|
||
} else {
|
||
NeopoolResponseError();
|
||
}
|
||
}
|
||
|
||
|
||
void CmndNeopoolOnError(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= NEOPOOL_MAX_REPEAT_ON_ERROR) {
|
||
neopool_repeat_on_error = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(neopool_repeat_on_error);
|
||
}
|
||
|
||
|
||
void CmndNeopoolPHRes(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 3) {
|
||
NeoPoolSettings.flags.ph = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(NeoPoolSettings.flags.ph);
|
||
}
|
||
|
||
|
||
void CmndNeopoolCLRes(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 3) {
|
||
NeoPoolSettings.flags.cl = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(NeoPoolSettings.flags.cl);
|
||
}
|
||
|
||
|
||
void CmndNeopoolIONRes(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 3) {
|
||
NeoPoolSettings.flags.ion = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(NeoPoolSettings.flags.ion);
|
||
}
|
||
|
||
|
||
void CmndNeopoolSetOption(void)
|
||
{
|
||
if (XdrvMailbox.index >= 0 && XdrvMailbox.index <= 1) {
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 1) {
|
||
bitWrite(NeoPoolSettings.flags.data, XdrvMailbox.index + 6, XdrvMailbox.payload);
|
||
}
|
||
ResponseCmndIdxChar(GetStateText(bitRead(NeoPoolSettings.flags.data, XdrvMailbox.index + 6)));
|
||
}
|
||
}
|
||
|
||
|
||
#ifdef NEOPOOL_EMULATE_GPERH
|
||
void CmndNeopoolgPerh(void)
|
||
{
|
||
if (XdrvMailbox.data_len && XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 1) {
|
||
neopool_system_gperh = XdrvMailbox.payload;
|
||
}
|
||
ResponseCmndNumber(neopool_system_gperh);
|
||
}
|
||
#endif
|
||
|
||
|
||
void NeopoolMqttShow(void) {
|
||
int tele_period_save = TasmotaGlobal.tele_period;
|
||
TasmotaGlobal.tele_period = 2;
|
||
ResponseClear();
|
||
ResponseAppendTime();
|
||
NeoPoolShow(true);
|
||
TasmotaGlobal.tele_period = tele_period_save;
|
||
ResponseJsonEnd();
|
||
MqttPublishTeleSensor();
|
||
}
|
||
|
||
|
||
void NeopoolCheckChanges(void) {
|
||
bool data_changed = false;
|
||
|
||
if ( 0 == NeoPoolSettings.npteleperiod ) {
|
||
return;
|
||
}
|
||
|
||
if (nullptr == NeoPoolRegCheckData) {
|
||
// alloc mem for compare data
|
||
NeoPoolRegCheckData = (uint16_t *)malloc(sizeof(*NeoPoolReg) * nitems(NeoPoolRegCheck));
|
||
if (nullptr != NeoPoolRegCheckData) {
|
||
memset(NeoPoolRegCheckData, 0, sizeof(*NeoPoolReg) * nitems(NeoPoolRegCheck));
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NEO: NeopoolCheckChanges - out of memory"));
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
}
|
||
|
||
for (uint32_t i = 0; nullptr != NeoPoolRegCheckData && i < nitems(NeoPoolRegCheck); i++) {
|
||
uint16_t data = NeoPoolGetData(pgm_read_word(NeoPoolRegCheck + i) & 0x0FFF);
|
||
if (NeoPoolRegCheckData[i] != data) {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: NeopoolCheckChanges() addr 0x%04X: data stored %d (0x%04X), data read %d (0x%04X)"),
|
||
pgm_read_word(NeoPoolRegCheck + i) & 0x0FFF,
|
||
NeoPoolRegCheckData[i], NeoPoolRegCheckData[i],
|
||
data, data);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
if (0 == (pgm_read_word(NeoPoolRegCheck + i) & 0xF000) || millis() > NeoPoolRegCheckDataTimeout) {
|
||
// changes only for undelayed measured value or time overrun
|
||
NeoPoolRegCheckData[i] = data;
|
||
data_changed = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: NeopoolCheckChanges() %s"), data_changed ? PSTR("true") : PSTR("false"));
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
if (data_changed) {
|
||
NeoPoolRegCheckDataTimeout = millis() + (NeoPoolSettings.npteleperiod * 1000);
|
||
NeopoolMqttShow();
|
||
}
|
||
}
|
||
|
||
|
||
void NeoPoolSettingsLoad(bool erase) {
|
||
char filename[20];
|
||
|
||
memset(&NeoPoolSettings, 0x00, sizeof(NeoPoolSettings));
|
||
NeoPoolSettings.crc32 = GetCfgCrc32((uint8_t*)&NeoPoolSettings +4, sizeof(NeoPoolSettings) -4);
|
||
NeoPoolSettings.version = NEOPOOL_SETTING_VERSION;
|
||
NeoPoolSettings.flags.ph = NEOPOOL_DEFAULT_PHRES;
|
||
NeoPoolSettings.flags.cl = NEOPOOL_DEFAULT_CLRES;
|
||
NeoPoolSettings.flags.ion = NEOPOOL_DEFAULT_IONRES;
|
||
NeoPoolSettings.flags.range_check = 1;
|
||
NeoPoolSettings.flags.conn_stat = 1;
|
||
NeoPoolSettings.result = NEOPOOL_DEFAULT_RESULT;
|
||
NeoPoolSettings.npteleperiod = NEOPOOL_DEFAULT_NPTELEPERIOD;
|
||
|
||
#ifdef USE_UFILESYS
|
||
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_SENSOR), XSNS_83);
|
||
if (erase) {
|
||
TfsDeleteFile(filename); // Use defaults
|
||
}
|
||
else if (TfsLoadFile(filename, (uint8_t*)&NeoPoolSettings, sizeof(NeoPoolSettings))) {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: Settings loaded from file '%s'"), filename);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
}
|
||
else {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
// File system not ready: No flash space reserved for file system
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: Use default settings as file system not ready or file '%s' not found"), filename);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
NeoPoolSettingsSave();
|
||
}
|
||
#else // USE_UFILESYS
|
||
AddLog(LOG_LEVEL_INFO, PSTR("NEO: No file system found, NeoPool uses default values and must be set manually"));
|
||
#endif // USE_UFILESYS
|
||
}
|
||
|
||
|
||
void NeoPoolSettingsSave(void) {
|
||
#ifdef USE_UFILESYS
|
||
uint32_t crc32 = GetCfgCrc32((uint8_t*)&NeoPoolSettings +4, sizeof(NeoPoolSettings) -4); // Skip crc32
|
||
if (crc32 != NeoPoolSettings.crc32) {
|
||
NeoPoolSettings.crc32 = crc32;
|
||
char filename[20];
|
||
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_SENSOR), XSNS_83);
|
||
if (TfsSaveFile(filename, (const uint8_t*)&NeoPoolSettings, sizeof(NeoPoolSettings))) {
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: saved to file '%s'"), filename);
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
}
|
||
#ifdef DEBUG_TASMOTA_SENSOR
|
||
else {
|
||
// File system not ready: No flash space reserved for file system
|
||
AddLog(LOG_LEVEL_DEBUG, PSTR("NEO: ERROR file system not ready or unable to save file '%s'"), filename);
|
||
}
|
||
#endif // DEBUG_TASMOTA_SENSOR
|
||
}
|
||
#endif // USE_UFILESYS
|
||
}
|
||
|
||
/****************************************************************************\
|
||
* Interface
|
||
\****************************************************************************/
|
||
|
||
bool Xsns83(uint32_t function)
|
||
{
|
||
bool result = false;
|
||
|
||
if (FUNC_INIT == function) {
|
||
NeoPoolInit();
|
||
} else if (neopool_active) {
|
||
switch (function) {
|
||
case FUNC_PRE_INIT:
|
||
NeoPoolSettingsLoad(false);
|
||
break;
|
||
case FUNC_SAVE_SETTINGS:
|
||
NeoPoolSettingsSave();
|
||
break;
|
||
case FUNC_EVERY_250_MSECOND:
|
||
NeoPoolPoll();
|
||
break;
|
||
case FUNC_EVERY_SECOND:
|
||
NeopoolCheckChanges();
|
||
break;
|
||
case FUNC_COMMAND:
|
||
result = DecodeCommand(kNPCommands, NPCommand);
|
||
break;
|
||
case FUNC_JSON_APPEND:
|
||
NeoPoolShow(true);
|
||
break;
|
||
case FUNC_AFTER_TELEPERIOD:
|
||
// remember time of last regular SENSOR publish
|
||
NeoPoolRegCheckDataTimeout = millis() + (NeoPoolSettings.npteleperiod * 1000);
|
||
break;
|
||
#ifdef USE_WEBSERVER
|
||
case FUNC_WEB_SENSOR:
|
||
NeoPoolShow(false);
|
||
break;
|
||
#endif // USE_WEBSERVER
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
#endif // USE_NEOPOOL
|