/**
* IOT Service
*/
package iot.basen;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.persistence.EntityManager;
import iot.model.dto.OnOffCounter;
import iot.model.dto.SolarPanelTempTrend;
import iot.model.dto.SolarPanelTempTrendDTO;
import iot.model.entity.MeasuredValue;
import iot.model.entity.Relay;
import iot.model.entity.RelayState;
import iot.model.entity.Sensor;
import iot.model.service.MeasuredValueService;
import iot.model.service.RelayService;
import iot.model.service.RelayStateService;
import iot.model.service.SensorService;
import iot.model.service.ServiceFactory;
import util.DateUtil;
import util.common.MailUtil;
/**
* Controls Relay that switch ON/OFF Basen pump to obtain maximum efficiency of Solar panels
*
* @author lvanek
*
*/
public class BasenPumpControllerJob implements Runnable
{
private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
private final SimpleDateFormat dateFormatOut = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss z", DateUtil.czechLocale);
/**
* Value formatter
*/
private final DecimalFormat valueFormatter = new DecimalFormat("0.0");
/**
* Code of relay controlling basen pump
*/
private final String codeRelayPump = "LV_RELAY_02";
/**
* Code of thermometer - Basen water
*/
private final String codeTempWater = "LV_TEMP_08";
/**
* Code of thermometer - Solar panel
*/
private final String codeTempSolarPanel = "LV_TEMP_11";
/**
* Threshold for switching pump ON
*/
private final float temperatureThreshold = 6.0f;
/**
* Maximum required temperature
*/
private final float temperatureRequired = 28.0f;
/**
* Default constructor
*/
public BasenPumpControllerJob()
{
}
/**
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
ZoneId zoneIdCET = ZoneId.of("CET");
final LocalDateTime now = LocalDateTime.now(zoneIdCET);
System.out.println("=========================================================");
System.out.println("BasenPumpControllerJob trigged by scheduler " + dateFormat.format(now));
try (ServiceFactory serviceFactory = ServiceFactory.createServiceFactory())
{
RelayService relayService = serviceFactory.getRelayService();
RelayStateService relayStateService = serviceFactory.getRelayStateService();
SensorService sensorService = serviceFactory.getSensorService();
MeasuredValueService measuredValueService = serviceFactory.getMeasuredValueService();
final Relay relay = relayService.getRelayByCode(codeRelayPump);
if (relay != null)
{
if (relay.getDateTerminated() != null)
{
return; // Relay is not active, do nothing
}
RelayState relayState = relayStateService.getLastRelayState(relay);
final LocalDateTime lastChange = LocalDateTime.ofInstant(relayState.getDateChange().toInstant(), ZoneId.of("CET"));
Duration duration = Duration.between(lastChange, now);
System.out.println("Relay " + codeRelayPump + " has last state: " + (relayState.getState() ? "ON" : "OFF") + ", last change before: " + String.valueOf(duration.getSeconds()) + " seconds");
/*
* Basen pump control logic
*/
final EntityManager em = serviceFactory.getEntityManager();
final Sensor sensorWaterTemp = sensorService.getSensorByCode(codeTempWater);
final Sensor sensorSolarPanelTemp = sensorService.getSensorByCode(codeTempSolarPanel);
final MeasuredValue waterTemp = measuredValueService.getLastMeasuredValue(sensorWaterTemp);
final MeasuredValue solarPanelTemp = measuredValueService.getLastMeasuredValue(sensorSolarPanelTemp);
System.out.println("Basen water: " + valueFormatter.format(waterTemp.getValue()) + ", Solar panel: " + valueFormatter.format(solarPanelTemp.getValue()));
OnOffCounter counter = getOnOffCount(relay, relayStateService);
//System.out.println(counter.toString());
System.out.println("Today statistics - OFF: " + counter.getSecondsOFF() / 60 + " min, ON: " + counter.getSecondsON() / 60 + " min.");
if (relayState.getState() && (duration.getSeconds() > 180)) // maximum pump running time: 3 min.
{
if ((now.getHour() >= 16) && (now.getHour() < 20) && counter.getSecondsON() < requiredCleaningTime(waterTemp.getValue()))
{
System.out.println("Leaving Pump ON to clean water");
System.out.println("---------------------------------------------------------");
return; // but if today's pump ON time is less than required, let it ON to clean water
}
if (solarPanelTemp.getValue() > (waterTemp.getValue() + temperatureThreshold))
{
System.out.println("Leaving Pump ON to cool Solar panel");
System.out.println("---------------------------------------------------------");
return; // Panel still so hot, continue with pumping
}
System.out.println("Switching relay " + codeRelayPump + " OFF ");
em.getTransaction().begin();
relayService.setState(relay, false); // Switch pump OFF
em.getTransaction().commit();
}
else if ((relayState.getState() == false) && (duration.getSeconds() > 600)) // minimum pump delay time: 10 min.
{
SolarPanelTempTrendDTO trendDTO = analyzeSolarPanelTempTrend(sensorSolarPanelTemp, waterTemp, measuredValueService);
System.out.println(trendDTO.getDescription());
if ((trendDTO.getSolarPanelTempTrend() == SolarPanelTempTrend.TEMPERATURE_STAGNATE_BUT_HOT) && (waterTemp.getValue() < temperatureRequired))
{
String subject = "Switching pump ON for water heating";
em.getTransaction().begin();
relayService.setState(relay, true); // Switch pump ON
em.getTransaction().commit();
System.out.println(subject);
MailUtil.sendMail(sensorSolarPanelTemp.getOwner().getMailRecipient(), subject, "Generated from IOT service.\r\n" + trendDTO.getDescription());
}
else if ((now.getHour() >= 16) && (now.getHour() < 20) && counter.getSecondsON() < requiredCleaningTime(waterTemp.getValue()))
{
String subject = "Switching pump ON for water cleaning";
em.getTransaction().begin();
relayService.setState(relay, true); // Switch pump ON
em.getTransaction().commit();
System.out.println(subject);
MailUtil.sendMail(sensorSolarPanelTemp.getOwner().getMailRecipient(), subject, "Generated from IOT service.\r\n" + trendDTO.getDescription());
}
}
}
}
catch (Exception e)
{
System.err.println(e.getLocalizedMessage());
}
System.out.println("=========================================================");
}
/**
* Estimate daily water cleaning time
*
* @param waterTemperature Temperature of water
* @return Cleaning time in seconds
*/
private long requiredCleaningTime(float waterTemperature)
{
if (waterTemperature < 20.0f)
{
return 60 * 15; // 15 min.
}
else if (waterTemperature < 25.0f)
{
return 60 * 30; // 30 min.
}
else if (waterTemperature < 28.0f)
{
return 60 * 60; // 1h
}
return 2 * 60 * 60; // 2 h
}
/**
* Analyze trends of temperatures to help to make decision if switch Pump ON
*
* @param sensorSolarPanelTemp Solar panel temperature Sensor
* @param waterTemp Basen water temperature
* @param measuredValueService Service for class MeasuredValue
* @return Analyzed trend
*/
private SolarPanelTempTrendDTO analyzeSolarPanelTempTrend(Sensor sensorSolarPanelTemp, MeasuredValue waterTemp , MeasuredValueService measuredValueService)
{
ZoneId zoneIdCET = ZoneId.of("CET");
final LocalDateTime now = LocalDateTime.now(zoneIdCET);
final LocalDateTime before20mins = now.minusMinutes(20);
final ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneIdCET);
final Date dateFrom = Date.from(before20mins.toInstant(zonedDateTime.getOffset()));
final Date dateTo = Date.from(now.toInstant(zonedDateTime.getOffset()));
System.out.println(DateUtil.getDateAsFullUTCStringWithTZ(dateFrom, DateUtil.czechLocale) + " - " + DateUtil.getDateAsFullUTCStringWithTZ(dateTo, DateUtil.czechLocale));
List<MeasuredValue> measuredValues = measuredValueService.getMeasuredValues(sensorSolarPanelTemp, dateFrom, dateTo);
StringBuilder description = new StringBuilder();
description.append("-----------------------------------------------------------------\n");
List<Float> changes = new ArrayList<>();
Float previousTemp = null;
for (MeasuredValue solarPanelTemp : measuredValues)
{
description.append("Solar panel: " + dateFormatOut.format(solarPanelTemp.getDateMeasured()) + " - " + valueFormatter.format(solarPanelTemp.getValue()) + " °C");
if (previousTemp != null)
{
Float change = solarPanelTemp.getValue() - previousTemp;
changes.add(change);
description.append(" change: " + valueFormatter.format(change) + "\n");
}
else
{
description.append("\n");
}
previousTemp = solarPanelTemp.getValue();
}
// Last change become first, etc ...
Collections.reverse(changes);
/*
* Analyze changes to decide if switch pump ON
*/
description.append("-----------------------------------------------------------------\n");
description.append("Water temperature " + valueFormatter.format(waterTemp.getValue()) + " °C\n");
SolarPanelTempTrend trend = SolarPanelTempTrend.NO_DATA;
if ((changes.size() >= 2) &&
(changes.get(0) <= (changes.get(1) + 0.5)) &&
((measuredValues.get(measuredValues.size() - 1).getValue()) > (waterTemp.getValue() + temperatureThreshold))
)
{
trend = SolarPanelTempTrend.TEMPERATURE_STAGNATE_BUT_HOT;
}
else if((changes.size() >= 2) &&
(changes.get(0) < 0.0 && (changes.get(1) < 0.5))
)
{
trend = SolarPanelTempTrend.TEMPERATURE_FALLS;
}
else if((changes.size() >= 2) &&
(changes.get(0) > 0.0 && (changes.get(1) > 0.0)) &&
(changes.get(0) >= (changes.get(1)))
)
{
trend = SolarPanelTempTrend.TEMPERATURE_RISES;
}
else if(changes.size() >= 2)
{
trend = SolarPanelTempTrend.OTHER;
}
description.append("Trend is: " + trend.name() + "\n");
return new SolarPanelTempTrendDTO(trend, description.toString());
}
/**
* Get today ON / OFF info
*
* @param relay Relay
* @param relayStateService Service for class RelayState
* @return ON / OFF seconds info
*/
private OnOffCounter getOnOffCount(Relay relay, RelayStateService relayStateService)
{
OnOffCounter counter = new OnOffCounter();
try
{
ZoneId zoneIdCET = ZoneId.of("CET");
final LocalDateTime now = LocalDateTime.now(zoneIdCET);
final LocalDateTime dayStart = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
final ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneIdCET);
final Date dateFrom = Date.from(dayStart.toInstant(zonedDateTime.getOffset()));
final Date dateTo = Date.from(now.toInstant(zonedDateTime.getOffset()));
//System.out.println(DateUtil.getDateAsFullUTCStringWithTZ(dateFrom, DateUtil.czechLocale) + " - " + DateUtil.getDateAsFullUTCStringWithTZ(dateTo, DateUtil.czechLocale));
List<RelayState> relayStates = relayStateService.getRelayStates(relay, dateFrom, dateTo);
LocalDateTime start = dayStart;
RelayState relayStateLast = null;
for (RelayState relayState : relayStates)
{
//System.out.println(dateFormatOut.format(relayState.getDateChange()) + " " + relayState.getState());
LocalDateTime lastChange = LocalDateTime.ofInstant(relayState.getDateChange().toInstant(), zoneIdCET);
Duration duration = Duration.between(start, lastChange);
start = lastChange;
relayStateLast = relayState;
if (relayState.getState())
{
counter.addSecondsOFF(duration.getSeconds());
}
else
{
counter.addSecondsON(duration.getSeconds());
}
}
if (relayStateLast != null)
{
LocalDateTime lastChange = LocalDateTime.ofInstant(relayStateLast.getDateChange().toInstant(), zoneIdCET);
Duration duration = Duration.between(lastChange, now);
if (relayStateLast.getState())
{
counter.addSecondsON(duration.getSeconds());
}
else
{
counter.addSecondsOFF(duration.getSeconds());
}
}
}
catch (Exception e)
{
System.err.println(e.getMessage());
}
return counter;
}
}