/*
 * 
 *  Samotech Switch Module SM308 Driver
 *	
 */


@Field String driverVersion = "v1.14 (25th November 2023)"
@Field boolean debugMode = false


#include BirdsLikeWires.library
import groovy.transform.Field

@Field String deviceMan = "Samotech"
@Field String deviceType = "Switch Module"

@Field int reportIntervalMinutes = 10
@Field int checkEveryMinutes = 2


metadata {

	definition (name: "$deviceMan $deviceType", namespace: "BirdsLikeWires", author: "Andrew Davison", importUrl: "https://raw.githubusercontent.com/birdslikewires/hubitat/master/samotech/drivers/samotech_switch_module_sm308.groovy") {

		capability "Actuator"
		capability "Configuration"
		capability "HealthCheck"
		capability "Refresh"
		capability "Switch"

		command "flash"

		attribute "healthStatus", "enum", ["offline", "online"]
		attribute "mode", "string"

		if (debugMode) {
			command "testCommand"
		}

		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,1000", outClusters: "0019", manufacturer: "Samotech", model: "SM308", deviceJoinName: "$deviceMan $deviceType SM308", application: "00"
		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,1000", outClusters: "0019", manufacturer: "Samotech", model: "SM308-S", deviceJoinName: "$deviceMan $deviceType SM308-S", application: "00"
		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0702,0B04,0B05,1000", outClusters: "0019", manufacturer: "Samotech", model: "SM308-2CH", deviceJoinName: "$deviceMan $deviceType SM308-2CH", application: "00"

	}

}


preferences {
	
	input name: "flashEnabled", type: "bool", title: "Enable flash", defaultValue: false
	input name: "flashRate", type: "number", title: "Flash rate (ms)", range: "500..5000", defaultValue: 1000

	if ("${getDeviceDataByName('model')}" == "SM308-2CH") {
		input name: "flashRelays", type: "enum", title: "Flash relay", options:[["FF":"Both"],["01":"Relay 1"],["02":"Relay 2"]]
	}

	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 main configure() method in BirdsLikeWires.library

	requestBasic()

	// Set device name.
	String deviceModel = getDeviceDataByName('model')
	device.name = "$deviceMan $deviceType $deviceModel"

	// Store relay count and create children.
	state.relayCount = ("${getDeviceDataByName('model')}" == "SM308-2CH") ? 2 : 1

	if (state.relayCount > 1) {
		for (int i = 1; i <= state.relayCount; i++) {
			fetchChild("hubitat", "Generic Component Switch", "0$i")
		}
	} else {
		deleteChildren()
	}

	// Always set to 'static' to ensure we're never stuck in 'flashing' mode.
	sendEvent(name: "mode", value: "static", isStateChange: false)

	// Reporting
	int reportIntervalSeconds = reportIntervalMinutes * 60
	sendZigbeeCommands(zigbee.configureReporting(0x0006, 0x0000, 0x0010, 0, reportIntervalSeconds, 1, [:], 200))	// Send switch status.

}


void updateSpecifics() {
	// Called by updated() method in BirdsLikeWires.library

	return

}


void ping() {

	logging("${device} : Ping", "info")
	refresh()

}


void refresh() {

	sendZigbeeCommands(["he rattr 0x${device.deviceNetworkId} 0xFF 0x0006 0x00 {}"])
	logging("${device} : Refreshed", "info")

}


void off() {

	sendZigbeeCommands(["he cmd 0x${device.deviceNetworkId} 0xFF 0x0006 0x00 {}"])
	sendEvent(name: "mode", value: "static")

}


void on() {

	sendZigbeeCommands(["he cmd 0x${device.deviceNetworkId} 0xFF 0x0006 0x01 {}"])
	sendEvent(name: "mode", value: "static")

}


void flash() {

	if (!flashEnabled) {
		logging("${device} : Flash : Disabled", "warn")
		return
	}

	if (!flashRelays && "${getDeviceDataByName('model')}" == "SM308-2CH") {
		logging("${device} : Flash : No relay chosen in preferences.", "warn")
		return
	}

	logging("${device} : Flash : Rate of ${flashRate ?: 1000} ms", "info")
	sendEvent(name: "mode", value: "flashing")
	pauseExecution 200
    flashOn()

}


void flashOn() {

	String mode = device.currentState("mode").value
	logging("${device} : flashOn : Mode is ${mode}", "debug")

    if (mode != "flashing") return
    runInMillis((flashRate ?: 1000).toInteger(), flashOff)

	String flashEndpoint = "FF"
	if (flashRelays) flashEndpoint = flashRelays

	sendZigbeeCommands(["he cmd 0x${device.deviceNetworkId} 0x${flashEndpoint} 0x0006 0x01 {}"])

}


void flashOff() {

	String mode = device.currentState("mode").value
	logging("${device} : flashOn : Mode is ${mode}", "debug")

    if (mode != "flashing") return
	runInMillis((flashRate ?: 1000).toInteger(), flashOn)

	String flashEndpoint = "FF"
	if (flashRelays) flashEndpoint = flashRelays

	sendZigbeeCommands(["he cmd 0x${device.deviceNetworkId} 0x${flashEndpoint} 0x0006 0x00 {}"])

}


void parse(String description) {

	updateHealthStatus()
	checkDriver()

	Map descriptionMap = zigbee.parseDescriptionAsMap(description)

	if (descriptionMap) {

		logging("${device} : Parse : ${descriptionMap}", "debug")
		processMap(descriptionMap)

	} else {
		
		logging("${device} : Parse : Failed to parse received data. Please report these messages to the developer.", "warn")
		logging("${device} : Parse : ${description}", "error")

	}

}


void processMap(Map map) {

	logging("${device} : processMap() : ${map}", "trace")

	if (map.cluster == "0006" || map.clusterId == "0006") {
		// Relay configuration and response handling.
		// State confirmation and command receipts.

		if (map.command == "01") {
			// Relay States (Refresh)

			if (map.value == "00") {

				if (state.relayCount > 1) {

					def childDevice = fetchChild("hubitat", "Generic Component Switch", "${map.endpoint}")
					childDevice.parse([[name:"switch", value:"off"]])

					def currentChildStates = fetchChildStates("switch","${childDevice.id}")
					logging("${device} : currentChildStates : ${childDevice.id} ${currentChildStates}", "debug")

					if (currentChildStates.every{it == "off"}) {
						logging("${device} : Switch : All Off", "info")
						sendEvent(name: "switch", value: "off")
					}

				} else {

					sendEvent(name: "switch", value: "off")

				}

				logging("${device} : Switch ${map.endpoint} : Off", "info")

			} else {

				if (state.relayCount > 1) {
					def childDevice = fetchChild("hubitat", "Generic Component Switch", "${map.endpoint}")
					childDevice.parse([[name:"switch", value:"on"]])
				}

				sendEvent(name: "switch", value: "on")
				logging("${device} : Switch ${map.endpoint} : On", "info")

			}

		} else if (map.command == "07") {
			// Relay Configuration

			logging("${device} : Relay Configuration : Successful", "info")

		} else if (map.command == "0A" || map.command == "0B") {
			// Relay States

			// Command "0A" is local actuation, command "0B" is remote actuation.

			// Some modules send an "0A" response after being switched remotely, which is a nice confirmation, but not correct behaviour.
			// Trick is, I've got one SM308-S which does this and one SM308-S which doesn't. Both fresh out of the box.

			// For the time being we're going to treat these as the same.
			// We'll be sure not to force our event with "isStateChange" as this would lead to double messaging.

			// I'll investigate this with a momentary switch when I get chance.

			String relayActuated = (map.command == "0A") ? map.endpoint : map.sourceEndpoint
			String relayState = (map.command == "0A") ? map.value : map.data[0]

			if (relayState == "00") {

				if (state.relayCount > 1) {

					def childDevice = fetchChild("hubitat", "Generic Component Switch", "$relayActuated")
					childDevice.parse([[name:"switch", value:"off"]])

					def currentChildStates = fetchChildStates("switch","${childDevice.id}")
					logging("${device} : currentChildStates : ${currentChildStates}", "debug")

					if (currentChildStates.every{it == "off"}) {

						debounceParentState("switch", "off", "All Devices Off", "info", 100)

					}

				} else {

					sendEvent(name: "switch", value: "off")

				}

				logging("${device} : Switched $relayActuated : Off", "info")

			} else {

				if (state.relayCount > 1) {
					def childDevice = fetchChild("hubitat", "Generic Component Switch", "$relayActuated")
					childDevice.parse([[name:"switch", value:"on"]])
				}

				sendEvent(name: "switch", value: "on")
				logging("${device} : Switched $relayActuated : On", "info")

			}

		} else if (map.command == "00") {

			logging("${device} : Skipped : State Counter Message", "debug")

		} else {

			filterThis(map)

		}

	} else if (map.cluster == "0702" || map.clusterId == "0702") {

		def int powerValue = zigbee.convertHexToInt(map.value)
		// sendEvent(name: "power", value: powerValue, unit: "W", isStateChange: false)
		// sendEvent(name: "powerWithUnit", value: "${powerValue} W", isStateChange: false)
		// logging("${device} : power hex value : ${map.value}", "trace")
		// logging("${device} : power sensor reports : ${powerValue}", "debug")

		logging("${device} : Power messaging seems broken. It looks more like energy logging. Received: ${powerValue} for endpoint ${map.endpoint}.", "debug")
		filterThis(map)

	} else {

		filterThis(map)

	}
	
}
