StaticTartanStateEvaluator.java
package tartan.smarthome.resources;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tartan.smarthome.resources.iotcontroller.IoTValues;
public class StaticTartanStateEvaluator implements TartanStateEvaluator {
private final Supplier<Long> timeProvider;
private static final Logger LOGGER = LoggerFactory.getLogger(StaticTartanStateEvaluator.class);
public StaticTartanStateEvaluator(Supplier<Long> timeProvider) {
this.timeProvider = timeProvider;
}
/**
* Helper to format log entries with timestamps
*
* @param entry The log message to format
* @return A formatted log entry with timestamp prefix
*/
private String formatLogEntry(String entry) {
Long timeStamp = timeProvider.get();
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm");
return "[" + sdf.format(new Date(timeStamp)) + "]: " + entry + "\n";
}
/**
* Helper to get current time (both real and faked)
*
* @return current hour in the form of 0-23 (0 midnight)
*/
private Long getCurrentTime() {
return (timeProvider.get() / (1000 * 60 * 60)) % 24;
}
/**
* Ensure the requested state is permitted. This method checks each state
* variable to ensure that the house remains in a consistent state.
*
* @param state The new state to evaluate
* @param log The log of state evaluations
* @return The evaluated state
*/
@SuppressWarnings("null")
@Override
public Map<String, Object> evaluateState(Map<String, Object> inState, StringBuilder log) {
// state variables that reflect the current configuration of the house
Integer tempReading = null; // the current temperature
Integer targetTempSetting = null; // the user-desired temperature setting
Boolean doorState = null; // the state of the door (true if open, false if closed)
Boolean lightState = null; // the state of the light (true if on, false if off)
Boolean proximityState = null; // the state of the proximity sensor (true of house occupied, false if vacant)
Boolean alarmState = null; // the alarm state (true if enabled, false if disabled)
Boolean humidifierState = null; // the humidifier state (true if on, false if off)
Boolean heaterOnState = false; // the heater state (true if on, false if off)
Boolean chillerOnState = false; // the chiller state (true if on, false if off)
Boolean alarmActiveState = null; // the alarm active state (true if alarm sounding, false if alarm not sounding)
Boolean awayTimerState = false; // assume that the away timer did not trigger this evaluation
String alarmPasscode = null; // the alarm passcode
String hvacState = null; // the HVAC mode setting, either Heater or Chiller
String givenPasscode = ""; // the passcode entered by the user
// smart lock variables
Boolean smartLockState = null; // the state of the smart lock (true if locked, false if unlocked)
String smartLockPasscode = null; // passcode for the smart lock
String enteredPasscode = ""; // The smart lock passcode entered by the user
Boolean nightLockState = false; // the night lock state (true if enabled, false if disabled)
String nightStartTime = null; // configured start time for night mode
String nightEndTime = null; // configured end time for night mode
String smartProxyId = null; // the ID for keyless entry
String givenProxyId = ""; // the ID 'detected' by the sensor
Boolean intruderDetected = null; // whether an intruder has been detected
Boolean allClear = true; // whether all clear (true if all clear, false if not all clear)
Boolean requiresPasscode = true; // if lock requires a passcode (true if requires passcode, false if not)
String experimentGroup = "1"; // the group for the A/B test
LOGGER.debug("Evaluating new state statically");
for (String key : inState.keySet()) {
switch (key) {
case IoTValues.TEMP_READING -> tempReading = (Integer) inState.get(key);
case IoTValues.TARGET_TEMP -> targetTempSetting = (Integer) inState.get(key);
case IoTValues.DOOR_STATE -> doorState = (Boolean) inState.get(key);
case IoTValues.LIGHT_STATE -> lightState = (Boolean) inState.get(key);
case IoTValues.PROXIMITY_STATE -> proximityState = (Boolean) inState.get(key);
case IoTValues.ALARM_STATE -> alarmState = (Boolean) inState.get(key);
case IoTValues.HUMIDIFIER_STATE -> humidifierState = (Boolean) inState.get(key);
case IoTValues.HVAC_MODE -> hvacState = (String) inState.get(key);
case IoTValues.ALARM_PASSCODE -> alarmPasscode = (String) inState.get(key);
case IoTValues.GIVEN_PASSCODE -> givenPasscode = (String) inState.get(key);
case IoTValues.AWAY_TIMER -> awayTimerState = (Boolean) inState.getOrDefault(key, false);
case IoTValues.ALARM_ACTIVE -> alarmActiveState = (Boolean) inState.get(key);
case IoTValues.SMART_LOCK_STATE -> smartLockState = (Boolean) inState.get(key);
case IoTValues.SMART_LOCK_PASSCODE -> smartLockPasscode = (String) inState.get(key);
case IoTValues.ENTERED_PASSCODE -> enteredPasscode = (String) inState.get(key);
case IoTValues.SMART_LOCK_REQUIRES_PASSCODE -> requiresPasscode = (Boolean) inState.get(key);
case IoTValues.NIGHT_START_TIME -> nightStartTime = inState.get(key).toString();
case IoTValues.NIGHT_END_TIME -> nightEndTime = inState.get(key).toString();
case IoTValues.SMART_PROXY_ID -> smartProxyId = (String) inState.get(key);
case IoTValues.GIVEN_PROXY_ID -> givenProxyId = (String) inState.get(key);
case IoTValues.INTRUDER_DETECTED -> intruderDetected = (Boolean) inState.get(key);
case IoTValues.ALL_CLEAR -> allClear = (Boolean) inState.get(key);
default -> log.append(formatLogEntry("Unknown state variable: " + key));
}
}
if (Boolean.TRUE.equals(awayTimerState)) {
lightState = false;
doorState = false;
alarmState = true;
awayTimerState = false;
smartLockState = true;
log.append(formatLogEntry("Away timer activated - securing house"));
}
if (Boolean.TRUE.equals(lightState)) {
// The light was activated
if (Boolean.FALSE.equals(proximityState)) {
log.append(formatLogEntry("Cannot turn on light because user not home"));
lightState = false;
} else {
log.append(formatLogEntry("Light on"));
}
} else {
log.append(formatLogEntry("Light off"));
}
// Handle door state when door is open
if (Boolean.TRUE.equals(doorState)) {
if (Boolean.FALSE.equals(proximityState)) {
// R3: If house is vacant, automatically close the door
doorState = false;
log.append(formatLogEntry("Closed door because house vacant"));
// R2: If alarm is enabled when door opens in vacant house, trigger alarm
if (Boolean.TRUE.equals(alarmState)) {
log.append(formatLogEntry("Break in detected: Activating alarm"));
alarmActiveState = true;
intruderDetected = true;
}
} else {
log.append(formatLogEntry("Door open"));
}
}
// The door is now closed
else if (Boolean.FALSE.equals(doorState)) {
// the door is closed - if the house is suddenly occupied this is a break-in
if (Boolean.TRUE.equals(alarmState) && Boolean.TRUE.equals(proximityState)) {
log.append(formatLogEntry("Break in detected: Activating alarm"));
alarmActiveState = true;
} else {
log.append(formatLogEntry("Closed door"));
}
}
// the user has arrived
if (Boolean.TRUE.equals(proximityState) && Boolean.FALSE.equals(awayTimerState)) {
log.append(formatLogEntry("House is occupied"));
// if the alarm has been disabled, then turn on the light for the user
if (Boolean.FALSE.equals(lightState) && Boolean.FALSE.equals(alarmState)) {
lightState = true;
log.append(formatLogEntry("Turning on light"));
}
}
// set the alarm
if (Boolean.TRUE.equals(alarmState)) {
log.append(formatLogEntry("Alarm enabled"));
} else if (Boolean.FALSE.equals(alarmState)) { // attempt to disable alarm
if (Boolean.FALSE.equals(proximityState)) {
alarmState = true;
log.append(formatLogEntry("Cannot disable the alarm, house is empty"));
}
if (Boolean.TRUE.equals(alarmActiveState)) {
if (!givenPasscode.equals(alarmPasscode)) {
log.append(formatLogEntry("Cannot disable alarm, invalid passcode given"));
alarmState = true;
} else {
log.append(formatLogEntry("Correct passcode entered, disabled alarm"));
alarmActiveState = false;
}
}
// If house is occupied and wrong passcode is entered, enable the alarm
else if (Boolean.TRUE.equals(proximityState) && !givenPasscode.equals(alarmPasscode)) {
alarmState = true;
log.append(formatLogEntry("Cannot disable the alarm, wrong passcode"));
}
}
if (Boolean.FALSE.equals(alarmState)) {
log.append(formatLogEntry("Alarm disabled"));
alarmActiveState = false;
}
// manage the HVAC control
if (tempReading < targetTempSetting) {
heaterOnState = true;
chillerOnState = false;
humidifierState = false;
hvacState = "Heater";
log.append(formatLogEntry(
"Heater activated - Target: " + targetTempSetting + "F, Current: " + tempReading + "F"));
} else if (tempReading > targetTempSetting) {
heaterOnState = false;
chillerOnState = true;
hvacState = "Chiller";
log.append(formatLogEntry(
"Cooling activated - Target: " + targetTempSetting + "F, Current: " + tempReading + "F"));
} else {
heaterOnState = false;
chillerOnState = false;
}
// NIGHT LOCK LOGIC
Long nightStart = Long.valueOf(nightStartTime);
Long nightEnd = Long.valueOf(nightEndTime);
Long currentTime = getCurrentTime();
if (nightStart <= nightEnd) {
if (currentTime >= nightStart && currentTime < nightEnd) {
log.append(formatLogEntry("Night lock ON - Current Time b/w : " + nightStart + " & " + nightEnd));
nightLockState = true;
smartLockState = true;
} else {
log.append(formatLogEntry("Night lock OFF - Outside night hours"));
nightLockState = false;
}
} else if (currentTime >= nightStart || currentTime < nightEnd) {
log.append(formatLogEntry("Night lock ON - Current Time b/w : " + nightStart + " & " + nightEnd));
nightLockState = true;
smartLockState = true;
} else {
log.append(formatLogEntry("Night lock OFF - Outside night hours"));
nightLockState = false;
}
// Intruder defense logic
if (Boolean.TRUE.equals(intruderDetected)) {
smartLockState = true;
doorState = false;
alarmActiveState = true;
lightState = true;
log.append(formatLogEntry("ALERT: Possible intruder detected - Full security response activated"));
if (Boolean.TRUE.equals(allClear) && givenPasscode != null && givenPasscode.equals(alarmPasscode)) {
intruderDetected = false;
log.append(formatLogEntry("All clear signal received with proper authorization"));
}
}
// Keyless Entry Logic
if (smartProxyId != null && givenProxyId != null && smartProxyId.equals(givenProxyId)) {
smartLockState = false;
log.append(formatLogEntry("Keyless entry authorized - Smart lock unlocked"));
}
// Smart lock logic
if (smartLockState != null) {
if (Boolean.TRUE.equals(nightLockState)) {
smartLockState = true;
} else if (Boolean.TRUE.equals(smartLockState)) {
if (Boolean.FALSE.equals(requiresPasscode)) {
smartLockState = false;
log.append(formatLogEntry("Smart lock unlocked - no passcode required"));
} else if (enteredPasscode != null && smartLockPasscode != null &&
enteredPasscode.equals(smartLockPasscode)) {
smartLockState = false;
log.append(formatLogEntry("Smart lock unlocked with valid passcode"));
}
}
}
Map<String, Object> newState = new HashMap<>();
newState.put(IoTValues.DOOR_STATE, doorState);
newState.put(IoTValues.AWAY_TIMER, awayTimerState);
newState.put(IoTValues.LIGHT_STATE, lightState);
newState.put(IoTValues.PROXIMITY_STATE, proximityState);
newState.put(IoTValues.ALARM_STATE, alarmState);
newState.put(IoTValues.HUMIDIFIER_STATE, humidifierState);
newState.put(IoTValues.HEATER_STATE, heaterOnState);
newState.put(IoTValues.CHILLER_STATE, chillerOnState);
newState.put(IoTValues.ALARM_ACTIVE, alarmActiveState);
newState.put(IoTValues.HVAC_MODE, hvacState);
newState.put(IoTValues.ALARM_PASSCODE, alarmPasscode);
newState.put(IoTValues.GIVEN_PASSCODE, givenPasscode);
newState.put(IoTValues.SMART_LOCK_STATE, smartLockState);
newState.put(IoTValues.NIGHT_LOCK_STATE, nightLockState);
newState.put(IoTValues.SMART_LOCK_PASSCODE, smartLockPasscode);
newState.put(IoTValues.NIGHT_START_TIME, nightStartTime);
newState.put(IoTValues.NIGHT_END_TIME, nightEndTime);
newState.put(IoTValues.INTRUDER_DETECTED, intruderDetected);
newState.put(IoTValues.SMART_PROXY_ID, smartProxyId);
newState.put(IoTValues.GIVEN_PROXY_ID, givenProxyId);
newState.put(IoTValues.ALL_CLEAR, allClear);
newState.put(IoTValues.SMART_LOCK_REQUIRES_PASSCODE, requiresPasscode);
newState.put(IoTValues.ENTERED_PASSCODE, enteredPasscode);
newState.put(IoTValues.EXPERIMENT_GROUP, experimentGroup);
return newState;
}
}