diff --git a/ArduinoPool/ArduinoPool.ino b/ArduinoPool/ArduinoPool.ino index 1ba3724..e1ee513 100644 --- a/ArduinoPool/ArduinoPool.ino +++ b/ArduinoPool/ArduinoPool.ino @@ -59,7 +59,7 @@ void loop() { if (systemState == SYSTEM_STATE_OFF) { Serial.println("Pool control is deactivated by server."); setArduinoPoolState(NO_ERRORS_STATE); - } else if (systemState == SYSTEM_STATE_NO_ERRORS) { + } else { for (int i = 0; i < 5; i++) { float temp = getTempInC(); if ((temp > TEMP_UNABLE_LOW) && (temp < TEMP_UNABLE_HIGH)) { diff --git a/ArduinoPump/ArduinoPump.ino b/ArduinoPump/ArduinoPump.ino index 35c171f..a844ec4 100644 --- a/ArduinoPump/ArduinoPump.ino +++ b/ArduinoPump/ArduinoPump.ino @@ -50,7 +50,7 @@ void loop() { Serial.println("'Read system state' max reached. Try reconnecting to Wifi..."); disablePump(); setArduinoPumpState(FAILED_READ_SYSTEM_STATE); - + if (reconnectToWifi() < 0) { Serial.println("Failed reconnecting to Wifi!"); } else { @@ -65,24 +65,36 @@ void loop() { Serial.println("Pool control is system-wide off."); disablePump(); setArduinoPumpState(NO_ERRORS_STATE); - } else if (systemState == SYSTEM_STATE_NO_ERRORS) { - bool arduinoPoolReachable = checkArduinoPoolLastConnection(); + } else { - if (!arduinoPoolReachable) { - Serial.println("ArduinoPool not reachable. Disableing pump."); + int mode = getControlMode(); + Serial.print("Control mode: "); + Serial.println(mode); + if (mode == CONTROL_MODE_ALWAYS_ON) { + activatePump(); + setArduinoPumpState(NO_ERRORS_STATE); + } else if (mode == CONTROL_MODE_ALWAYS_OFF) { disablePump(); - setArduinoPumpState(TEMP_DATA_OUTDATED); + setArduinoPumpState(NO_ERRORS_STATE); } else { - double poolTemp = getPoolTemperature(); - double requestedTemp = getRequestedTemperature(); - if (poolTemp <= -127.0 || requestedTemp <= -127.0) { - Serial.println("Failed reading temperatures from server."); + bool arduinoPoolReachable = checkArduinoPoolLastConnection(); + if (!arduinoPoolReachable) { + Serial.println("ArduinoPool not reachable. Disableing pump."); disablePump(); - setArduinoPumpState(FAILED_READ_TEMP); + setArduinoPumpState(TEMP_DATA_OUTDATED); } else { - controlPump(poolTemp, requestedTemp); - setArduinoPumpState(NO_ERRORS_STATE); + double poolTemp = getPoolTemperature(); + double requestedTemp = getRequestedTemperature(); + + if (poolTemp <= -127.0 || requestedTemp <= -127.0) { + Serial.println("Failed reading temperatures from server."); + disablePump(); + setArduinoPumpState(FAILED_READ_TEMP); + } else { + controlPump(poolTemp, requestedTemp); + setArduinoPumpState(NO_ERRORS_STATE); + } } } } diff --git a/ArduinoPump/Firebase.ino b/ArduinoPump/Firebase.ino index 79012f9..af1556d 100644 --- a/ArduinoPump/Firebase.ino +++ b/ArduinoPump/Firebase.ino @@ -45,6 +45,10 @@ double getRequestedTemperature(void) { return getTemperatureFromFB(requestedTemperaturePath + "temperature", -127.0); } +int getControlMode(void) { + return getIntFromFB(requestedTemperaturePath + "control-mode", -1); +} + unsigned long long getArduinoPoolLastConnection(void) { return getTimestampFromFB(arduinoPoolPath + "last-connection"); } diff --git a/ArduinoPump/constants.h b/ArduinoPump/constants.h index 7b7871a..c67db99 100644 --- a/ArduinoPump/constants.h +++ b/ArduinoPump/constants.h @@ -18,4 +18,8 @@ #define READ_SYSTEM_STATE_TRYS_MAX 5 #define RESET_FIREBASE_TRYS_MAX 5 +#define CONTROL_MODE_AUTOMATIC 0 +#define CONTROL_MODE_ALWAYS_ON 1 +#define CONTROL_MODE_ALWAYS_OFF 2 + #define DATA_TO_OLD_DIF 3660000 diff --git a/geha_pool_webapp/lib/providers/db_provider.dart b/geha_pool_webapp/lib/providers/db_provider.dart index 6426463..0dd79e5 100644 --- a/geha_pool_webapp/lib/providers/db_provider.dart +++ b/geha_pool_webapp/lib/providers/db_provider.dart @@ -228,15 +228,23 @@ class PumpData { } const _REQTEMP_LAST_UPDATE_KEY = "last-update"; +const _REQTEMP_CONTROL_MODE_KEY = "control-mode"; const _REQTEMP_TEMPERATURE_KEY = "temperature"; const _REQTEMP_UPDATE_UID_KEY = "update-uid"; const MIN_TEMP = 0; const MAX_TEMP = 40; -/// Temperature the requested temp is set to, when user wants to disable the pump. -/// Should be way under normal temperature values. -const _PUMP_DISABLED_TEMP = -100; +enum ControlMode { + automatic, + alwaysOn, + alwaysOff, +} + +ControlMode _controlModeByInt(int? state) { + if (state == null) return ControlMode.automatic; + return ControlMode.values[state]; +} class ReqTempData { ReqTempData(this._dbRef); @@ -244,32 +252,41 @@ class ReqTempData { DatabaseReference _dbRef; int? _lastUpdate; int? _temperature; + int? _controlMode; DateTime? get lastUpdate => _dateTimeByIntTimestamp(_lastUpdate); String? get lastUpdateFormatted => _dateTimeFormatted(lastUpdate); int? get temperature => _temperature; + ControlMode get controlMode => _controlModeByInt(_controlMode); + + @deprecated bool? get active => temperature != null ? temperature! >= MIN_TEMP : null; void _setFromJSON(Map json) { - _lastUpdate = json[_ARPUMP_LAST_UPDATE_KEY]; + _lastUpdate = json[_REQTEMP_LAST_UPDATE_KEY]; _temperature = json[_REQTEMP_TEMPERATURE_KEY]; + _controlMode = json[_REQTEMP_CONTROL_MODE_KEY]; } Future setTemperature(UserProvider users, int temperature) async { if (!users.isSignedIn) { - throw StateError("Temperature can only be changed when logged in."); + print("Temperature can only be changed when logged in."); + return false; } if (temperature > MAX_TEMP) { - throw ArgumentError("Requested temperature must be <= $MAX_TEMP."); + print("Requested temperature must be <= $MAX_TEMP."); + return false; } if (temperature < MIN_TEMP) { - temperature = _PUMP_DISABLED_TEMP; + print("Requested temperature must be >= $MIN_TEMP."); + return false; } try { final timestamp = DateTime.now().millisecondsSinceEpoch; await _dbRef.update({ _REQTEMP_LAST_UPDATE_KEY: timestamp, + _REQTEMP_CONTROL_MODE_KEY: ControlMode.automatic.index, _REQTEMP_TEMPERATURE_KEY: temperature, _REQTEMP_UPDATE_UID_KEY: users.user!.uid, }); @@ -279,6 +296,26 @@ class ReqTempData { return true; } + Future setControlMode(UserProvider users, ControlMode mode) async { + if (!users.isSignedIn) { + print("Control mode can only be changed when logged in."); + return false; + } + + try { + final timestamp = DateTime.now().millisecondsSinceEpoch; + await _dbRef.update({ + _REQTEMP_LAST_UPDATE_KEY: timestamp, + _REQTEMP_CONTROL_MODE_KEY: mode.index, + _REQTEMP_UPDATE_UID_KEY: users.user!.uid, + }); + } catch (_) { + print(_); + return false; + } + return true; + } + void _onDbDataUpdated(Event event) { final key = event.snapshot.key; final value = event.snapshot.value; @@ -287,6 +324,8 @@ class ReqTempData { _lastUpdate = value; } else if (key == _REQTEMP_TEMPERATURE_KEY) { _temperature = value; + } else if (key == _REQTEMP_CONTROL_MODE_KEY) { + _controlMode = value; } } } diff --git a/geha_pool_webapp/lib/strings.dart b/geha_pool_webapp/lib/strings.dart index 57e3f01..7fd0ce2 100644 --- a/geha_pool_webapp/lib/strings.dart +++ b/geha_pool_webapp/lib/strings.dart @@ -14,7 +14,7 @@ class Strings { static String get requestedTemperature => _STRINGS['requested_temperature']!; static String get noRequestedTemperature => _STRINGS['no_requested_temperature']!; - static String get changeTemperature => _STRINGS['change_temperature']!; + static String get change => _STRINGS['change']!; static String get logIn => _STRINGS['log_in']!; static String get email => _STRINGS['email']!; static String get password => _STRINGS['password']!; @@ -41,6 +41,7 @@ class Strings { static String get failedReadingSysStatePump => _STRINGS['failed_reading_sys_state_pump']!; static String get tryAgain => _STRINGS['try_again']!; + static String get manualMode => _STRINGS['manual_mode']!; } const _STRINGS = { @@ -55,7 +56,7 @@ const _STRINGS = { "system_is_in_maintenance": "Das System ist in Wartung.", "requested_temperature": "Wunschtemperatur", "no_requested_temperature": "Keine Wunschtemperatur festgelegt.", - "change_temperature": "Temperatur ändern", + "change": "Ändern", "log_in": "Anmelden", "email": "E-Mail:", "password": "Passwort:", @@ -82,5 +83,6 @@ const _STRINGS = { "Da der System-Status nicht gelesen werden kann, wird die Temperatur nicht aktualisiert.", "failed_reading_sys_state_pump": "Da der System-Status nicht gelesen werden kann, wurde die Pumpe deaktiviert.", - "try_again": "Erneut versuchen" + "try_again": "Erneut versuchen", + "manual_mode": "Manueller Modus", }; diff --git a/geha_pool_webapp/lib/widgets/change_temperature.dart b/geha_pool_webapp/lib/widgets/change_temperature.dart index aa80108..baef72f 100644 --- a/geha_pool_webapp/lib/widgets/change_temperature.dart +++ b/geha_pool_webapp/lib/widgets/change_temperature.dart @@ -32,31 +32,57 @@ class ChangeTemperatureDialog extends StatefulWidget { } class _ChangeTemperatureDialogState extends State { - int? temperature; + static const controlModeOffPoint = (MIN_TEMP - 1); + static const controlModeOnPoint = (MAX_TEMP + 1); + + int? sliderPoint; bool failedUpdating = false; - void updateTemperature(ReqTempData reqTemp) async { - if (temperature != null) { - if (await reqTemp.setTemperature(widget.userProvider, temperature!)) { - Navigator.pop(context); + void updateReqTemperature(ReqTempData reqTemp) async { + if (sliderPoint != null) { + final mode = controlModeBySliderPoint; + if (mode == ControlMode.automatic) { + if (await reqTemp.setTemperature(widget.userProvider, sliderPoint!)) { + Navigator.pop(context); + } else { + setState(() => failedUpdating = true); + } } else { - setState(() => failedUpdating = true); + if (await reqTemp.setControlMode(widget.userProvider, mode)) { + Navigator.pop(context); + } else { + setState(() => failedUpdating = true); + } } } } + ControlMode get controlModeBySliderPoint { + if (this.sliderPoint! >= controlModeOnPoint) return ControlMode.alwaysOn; + if (this.sliderPoint! <= controlModeOffPoint) return ControlMode.alwaysOff; + return ControlMode.automatic; + } + + String get temperatureText { + if (controlModeBySliderPoint == ControlMode.alwaysOn) return "ON"; + if (controlModeBySliderPoint == ControlMode.alwaysOff) return "OFF"; + return "$sliderPoint°C"; + } + @override Widget build(BuildContext context) { return Consumer( builder: (context, dbProvider, child) { final reqTemp = dbProvider.reqTempData; - if (temperature == null) { - if (reqTemp.temperature == null) { - temperature = 20; - } else { - reqTemp.active! - ? temperature = reqTemp.temperature - : temperature = (MIN_TEMP - 1); + if (sliderPoint == null) { + if (reqTemp.controlMode == ControlMode.alwaysOff) { + sliderPoint = controlModeOffPoint; + } else if (reqTemp.controlMode == ControlMode.alwaysOn) { + sliderPoint = controlModeOnPoint; + } else if (reqTemp.controlMode == ControlMode.automatic) { + reqTemp.temperature == null + ? sliderPoint = 20 + : sliderPoint = reqTemp.temperature; } } @@ -68,17 +94,17 @@ class _ChangeTemperatureDialogState extends State { Padding( padding: const EdgeInsets.all(24.0), child: Text( - this.temperature! >= MIN_TEMP ? "$temperature°C" : "OFF", + temperatureText, style: Theme.of(context).textTheme.headline4, ), ), Slider( - value: temperature!.toDouble(), + value: sliderPoint!.toDouble(), onChanged: (newTemp) { - setState(() => temperature = newTemp.round()); + setState(() => sliderPoint = newTemp.round()); }, min: (MIN_TEMP - 1).toDouble(), - max: MAX_TEMP.toDouble(), + max: (MAX_TEMP + 1).toDouble(), ), Text( Strings.setRequestedTemperature, @@ -107,7 +133,7 @@ class _ChangeTemperatureDialogState extends State { child: Text(Strings.cancel), ), TextButton( - onPressed: () => updateTemperature(reqTemp), + onPressed: () => updateReqTemperature(reqTemp), child: Text(Strings.save), ), ], diff --git a/geha_pool_webapp/lib/widgets/dashboard.dart b/geha_pool_webapp/lib/widgets/dashboard.dart index 1de6832..fb78110 100644 --- a/geha_pool_webapp/lib/widgets/dashboard.dart +++ b/geha_pool_webapp/lib/widgets/dashboard.dart @@ -125,15 +125,17 @@ class ReqTemp extends StatelessWidget { final ReqTempData reqTemp; String get tempText { - if (reqTemp.temperature == null) { - return Strings.noRequestedTemperature; - } - if (!reqTemp.active!) { - return "< 0°C"; - } + if (reqTemp.temperature == null) return Strings.noRequestedTemperature; + if (reqTemp.controlMode == ControlMode.alwaysOff) return "OFF"; + if (reqTemp.controlMode == ControlMode.alwaysOn) return "ON"; return "${reqTemp.temperature}°C"; } + String get subtitleTempText { + if (reqTemp.controlMode != ControlMode.automatic) return Strings.manualMode; + return Strings.requestedTemperature; + } + void changeTemperature(BuildContext context) { showDialog(context: context, builder: (context) => ChangeTemperature()); } @@ -158,7 +160,7 @@ class ReqTemp extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 2.0, bottom: 6.0), child: Text( - Strings.requestedTemperature, + subtitleTempText, style: Theme.of(context).textTheme.bodyText1, textAlign: TextAlign.center, ), @@ -167,7 +169,7 @@ class ReqTemp extends StatelessWidget { padding: const EdgeInsets.only(top: 6.0, bottom: 12.0), child: MaterialButton( onPressed: () => changeTemperature(context), - child: Text(Strings.changeTemperature), + child: Text(Strings.change), textColor: Colors.blue, ), ),