/*
 * 
 *  Hive Receiver Heating Driver
 *	
 */


@Field String driverVersion = "v0.70 (6th September 2023)"
@Field boolean debugMode = false


#include BirdsLikeWires.library
import groovy.json.JsonOutput
import groovy.transform.Field

@Field int reportIntervalMinutes = 1
@Field int checkEveryMinutes = 4
@Field int receiverEndpoint = 5

metadata {

	definition (name: "Hive Receiver Heating", namespace: "BirdsLikeWires", author: "Andrew Davison", importUrl: "https://raw.githubusercontent.com/birdslikewires/hubitat/master/hive/drivers/hive_receiver_heating.groovy") {

		capability "Actuator"
		capability "Configuration"
		capability "Refresh"
        capability "Sensor"
		capability "TemperatureMeasurement"
		capability "Thermostat"
		capability "ThermostatHeatingSetpoint"
		capability "ThermostatMode"
		capability "ThermostatOperatingState"
		capability "ThermostatSetpoint"

		attribute "overrideMinutes", "number"
		attribute "healthStatus", "enum", ["offline", "online"]

		if (debugMode) {
			command "testCommand"
		}

		fingerprint profileId: "0104", inClusters: "0000,0003,0009,000A,0201,FD00", outClusters: "000A,0402,0019", manufacturer: "Computime", model: "SLR2", deviceJoinName: "Computime Boiler Controller SLR2"

	}

}


preferences {
	
	input name: "infoLogging", type: "bool", title: "Enable logging", defaultValue: true
	input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: false
	input name: "traceLogging", type: "bool", title: "Enable trace logging", defaultValue: false
	
}


void testCommand() {

	logging("${device} : Test Command", "info")

}


void configureSpecifics() {
	// Called by general configure() method

	device.name = "${getParent().device.name} Heating"
	device.label = "${getParent().device.label} (Heating)"
	refresh()

	// Set expected modes and values.
	sendEvent(name: "supportedThermostatModes", value: JsonOutput.toJson(["auto", "emergency heat", "heat", "off"]))
	sendEvent(name: "supportedThermostatFanModes", value: JsonOutput.toJson(["auto"]))
	sendEvent(name: "thermostatFanMode", value: "auto", isStateChange: false)

}




void updateSpecifics() {
	// Called by library updated() method

	return

}


void refresh() {

	getOccupiedHeatingSetpoint()
	logging("${device} : Refreshed", "info")

}


void auto() {
	
	getParent().auto(receiverEndpoint)

}


void cool() {

	getParent().cool(receiverEndpoint)

}


void setCoolingSetpoint(BigDecimal temperature) {

	getParent().setCoolingSetpoint(receiverEndpoint, temperature)

}

void emergencyHeat() {

	getParent().emergencyHeat(receiverEndpoint)

}


void fanAuto() {

	getParent().fanAuto(receiverEndpoint)

}


void fanCirculate() {

	getParent().fanCirculate(receiverEndpoint)

}


void fanOn() {

	getParent().fanOn(receiverEndpoint)

}


void heat() {

	getParent().heat(receiverEndpoint)

}


void setHeatingSetpoint(BigDecimal temperature) {

	getParent().setHeatingSetpoint(receiverEndpoint, temperature)

}


void setThermostatMode(String thermostatMode) {

	getParent().setThermostatMode(receiverEndpoint, thermostatMode)
	
}


void setThermostatFanMode(String fanMode) {

	getParent().setThermostatFanMode(receiverEndpoint, fanMode)
	
}


void off() {

	getParent().off(receiverEndpoint)

}


void getOccupiedHeatingSetpoint() {

	getParent().getOccupiedHeatingSetpoint(receiverEndpoint)

}


void getSystemMode() {

	getParent().getSystemMode(receiverEndpoint)

}


void getTemperatureSetpointHold() {

	getParent().getTemperatureSetpointHold(receiverEndpoint)

}


void getTemperatureSetpointHoldDuration() {

	getParent().getTemperatureSetpointHoldDuration(receiverEndpoint)

}


void getThermostatRunningState() {

	getParent().getThermostatRunningState(receiverEndpoint)

}


void processMap(Map map) {

	updateHealthStatus()
	checkDriver()

	if (map.cluster == "0201") {
		// Thermostat Cluster

		if (map.attrId == "0000" || map.attrId == "0012") {
			// Temperature or OccupiedHeatingSetpoint

			String temperatureType = ("${map.attrId}" == "0000") ? "temperature" : "heatingSetpoint"

			BigDecimal temperature = hexStrToSignedInt(map.value)
			temperature = temperature / 100
			temperature = temperature.setScale(1, BigDecimal.ROUND_DOWN)  // They seem to round down for the stat display.

			logging("${device} : ${temperatureType} : ${temperature} from hex value ${map.value} ", "debug")

			String temperatureScale = location.temperatureScale
			if (temperatureScale == "F") {
				temperature = (temperature * 1.8) + 32
			}

			logging("${device} : ${temperatureType} : ${temperature} Â°${temperatureScale}", "info")
			sendEvent(name: "${temperatureType}", value: temperature, unit: "${temperatureScale}")

			if (temperatureType == "heatingSetpoint") {

				// System is heating-only. The cooling setpoint can only ever be our heating target.
				sendEvent(name: "coolingSetpoint", value: temperature, unit: "${temperatureScale}")
				sendEvent(name: "thermostatSetpoint", value: temperature, unit: "${temperatureScale}")

				// Now we need to check whether this was a scheduled or manual setpoint change.
				getSystemMode()
				getTemperatureSetpointHoldDuration()

			}

		} else if (map.attrId == "001C") {

			// Received mode data.
			logging("${device} : mode : ${map.value} on endpoint ${map.endpoint}", "debug")

			switch(map.value) {

				case "00":
					// This always represents off.
					logging("${device} : mode : heating off", "debug")
					sendEvent(name: "thermostatMode", value: "off")
					getThermostatRunningState()
					break
				case "04":
					// This always represents normal heating, but we must request the hold state to know if we're scheduled or manual.
					logging("${device} : mode : heating on - requesting hold state", "debug")
					getTemperatureSetpointHold()
					break
				case "05":
					// This always represents boost mode.
					logging("${device} : mode : heating boost", "debug")
					sendEvent(name: "thermostatMode", value: "emergency heat")
					break
				default:
					logging("${device} : mode : unknown heating mode received", "warn")
					break

			}

		} else if (map.attrId == "0023") {
			// TemperatureSetpointHold

			logging("${device} : setpoint hold : ${map.value} on endpoint ${map.endpoint}", "debug")

			switch(map.value) {

				case "00":
					// This always represents scheduled mode as the hold is off, so the schedule is running.
					sendEvent(name: "thermostatMode", value: "auto")
					logging("${device} : setpoint hold : ${map.value} on endpoint ${map.endpoint}", "debug")
					break
				case "01":
					// The hold is on, so the schedule is paused. We're in some sort of manual mode.
					//   NOTE! If the system was in schedule (auto) mode and the setpoint altered without changing from that mode,
					//         the system switches to a hybrid mode where the altered setpoint runs for the duration until the
					//         next programme change in the schedule. At that point the programme resumes, but the thermostat
					//         always reports "SCH" despite being in a manual state.
					sendEvent(name: "thermostatMode", value: "heat")
					break

			}

		} else if (map.attrId == "0024") {
			// TemperatureSetpointHoldDuration

			BigDecimal holdDuration = hexStrToSignedInt(map.value)
			(holdDuration < 0) ? holdDuration = 0 : holdDuration

			logging("${device} : overrideMinutes : ${holdDuration} (${map.value}) on endpoint ${map.endpoint}", "debug")

			sendEvent(name: "overrideMinutes", value: holdDuration)

		} else if (map.attrId == "0029") {
			// ThermostatRunningState

			logging("${device} : thermostatOperatingState : ${map.value} on endpoint ${map.endpoint}", "debug")

			switch(map.value) {

				case "0000":
					sendEvent(name: "thermostatOperatingState", value: "idle")
					logging("${device} : thermostatOperatingState : idle", "debug")
					break
				case "0001":
					sendEvent(name: "thermostatOperatingState", value: "heating")
					logging("${device} : thermostatOperatingState : heating", "debug")
					break
				default:
					logging("${device} : thermostatOperatingState : Received an unknown boiler control state!", "warn")
					break

			}

		} else {

			filterThis(map)

		}

	} else {

		filterThis(map)

	}
	
}
