Raspberry Pi with PHP (or Arduino/ESP8266/NodeMCU) to GPIO to Relay to Gate/Garage Trigger

Thanks for the reply @Jason_Brown… about to post a new update with this important tidbit.

Huge update with many fixes and enhancements.

v1.0.20170108 - ESP8266/NodeMCU can now be wired with an ENC28J60 using modified UIPEthernet library. Ability to use transistor/mosfet rather than relays. Reboot is now optionally modified via the web-page & stored in the first byte of the EEPROM thus the setting will survive a reboot or a flash. ST code now will work with an invalid header (impacts ESP8266 & ENC28J60 unreliability). Wiring diagrams updated.

On the ESP8266 if wanting to use wired Ethernet via the ENC28J60 module, you must use this modified UIPEthernet Library, originally done by @ntruchsess then updated for use with ESP8266 by @cassyarduino on this post. New wiring diagrams created with the optional ENC28J60. Using the ENC28J60 is unfortunately inconsistent and things like Android/IOS can’t read the web page while a desktop browser can. SmartThings was also failing due to my restricted code where I was expecting a valid header. At this point, the ST code was modified to work even if the header is garbage but the HTML is good. FYI, the ENC28J60 will run a bit hot on a higher output power source and good USB cable. It ran much cooler from a lower powered computer USB port. Buying cheap, small heat sinks just in case.

Transistor or a mosfet can now be used as the short/trigger mechanism instead of the bulky & mechanical relays. New wiring diagram contains this new triggering option. Top of the Arduino sketch has an important variable called use5Vrelay which should be false for this type of a rig. I did have to use a diode otherwise the web page would always show a wrong status for pins and that’s important to be correct, hence those diodes on the transistor base in the new wiring diagram.

After 7 days of uptime on multiple ESP8266 modules (CH340G & WeMos D1 Mini), I feel the reboots should not be forced on folks. I have made it optional and it can only be changed/set via the web page on the device. It will store it in the first byte of the EEPROM so it can range from 0 days (means off) to 255 days. The setting will survive reboots and flashes so be mindful of it.

Enjoy and please feel free to provide any feedback or issues.

1 Like

New version released:

v1.0.20170110 - Arduino & NodeMCU code updated. Stateful on/off functionality fixed. DHT sensor will now retry 5 times so should return results with more success than before. DHT part number displayed.

Had some bugs on the last one where a carriage return was making ST not recognize if button is on or off. For momentary functions (like I mostly use), there was no impact. Enhanced functionality where the DHT sensor will retry 5 times in trying to get temperature & humidity. Posting on the site which DHT sensor part number is being used.

If you downloaded the January 8th version of the Arduino sketches, please re-download Jan 10 version, it’ll be worth it :slight_smile:

Wow! i havent visited in a while and it looks like there has been some great updates! well done.

Any thoughts on OTA updates?

Glad you like it!

I’ve looked at OTA but it seems a bit painful especially when the design of this project is set it up once and hopefully forget it :slight_smile:

Hey @cypher2001 I was totally wrong. Didn’t take much… the first link didn’t quite work yet it’s the more impressive, direct to IDE version of OTA. It ultimately failed for me plabably due to lack of patience on my behalf :slight_smile:

However, the second link did work great and I had no issues weaving it into the code. Let me know if interested in testing it and I’ll get it to you.

  1. Directly doing OTA via the Arduino IDE
  2. Using a web page on the ESP to upload the bin file.

EDIT: Both methods now work, a locally hosted page and a remote network port via Arduino IDE. Thanks for making me research further!

Absolutely… I’d love to help however possible. I only asked because you are putting the effort into development of this and evolving additional feature functionality. I’m sure a mechanism to implement these changes quickly would be well received my everyone here.

New version uploaded.

v1.0.20170121 - NodeMCU sketch is now able to do OTA updates via a web-page or directly via the Arduino IDE.

For folks unfamiliar, OTA is “over the air” updating. The ESP8266/NodeMCU is able to do this via two ways and both are supported in my project.

  1. Directly doing OTA via the Arduino IDE
  2. Using a web page on the ESP to upload the bin file

Another update with the following notes:

v1.0.20170126 - NodeMCU sketch DHT sensor retry logic is a bit smarter. Small bug fixes.

Screenshot for Arduino also updated.

@JZst Any plans to make the custom trigger visible in CoRE? It would be cool to set a reminder if the custom trigger is open for too long etc.

Right now, CoRE can only see the main trigger, and with it being momentary (for my needs anyway) it isn’t suitable for that sort of reminder.

Not sure if this is even possible, or how much work would be involved.

Hey @boogy886 I wish this was easy but it’s not. Being the devil’s advocate, a momentary switch is not the best to use in rules, etc. because you never really know if your on/off actually turned the device on or off. It would be smarter to link the second event (customTrigger) to some sensor and now you have a virtual button and a state. It’s much harder when using the customTrigger to do a garage light like I do. Which means I never know its state via ST other than looking at a camera. I’ll try to work on something — likely a SmartApp which creates a virtual button/switch for that customTrigger. No promises though as I’ve never produced code of that nature, but I’ll give it a shot. Let me know if any of the above does not make sense… this is always the dilemma when using momentary functionality.

@JZst I completely agree. In fact, that’s exactly my setup. The mainTrigger on/off is the relay that controls my garage door, and the customTrigger on my setup is a contact that shows opened/closed. I have a Z-Wave switch that controls the lights in my garage.

So if the customTrigger was visible in CoRE, it could be used at least for reminder rules. And even further than that. I’m thinking for example:

  1. Happens every 5 minutes -> Poll customTrigger
  2. If after 10 minutes -> customTrigger is open -> then send reminder.
  3. If after midnight -> customTrigger is open -> and Night mode is active (meaning I’m in bed) -> trigger GarageDoor.

@boogy886 can you upload your code somewhere for me? I can take a look… keep in mind that my device does NOT use a contact sensor “capability” but it can easily be the current custom/secondary state. I have envisioned that folks would use the second function as another switch rather than sensor but your use-case is realistic so I may be able to oblige and likely with ease.

Answer this question for me, what kind of contact switch are you using for that custom function? Just wondering about the physical device itself. I may need to get something like it if my blind attempts fail.

@JZst Sure. I have this old script running because I never stopped it from my old method of control. I’m not sure if it’s necessary or not though. I actually haven’t thought much about it until just now. I left it running as a fail safe in case this SmartThings toy was junk…(I’ve been pleasantly surprised with SmartThings thanks to the openness and the community driven improvements) but, I digress.

This would be using RPi.GPIO to set the pin as IN rather than OUT.

Import RPi.GPIO

def __init__(self, config):
     self.state_pin = 27
     gpio.setup(self.state_pin, gpio.IN, pull_up_down=gpio.PUD_UP)
def get_state(self):
    if gpio.input(self.state_pin) == 0:
        return 'closed'
    else:
        return 'open'

The magnetic contact I’m using changes the input on the Pi. If the magnets are in contact with each other, it returns a 0. If the circuit is open, it returns a 1. If I remember correctly, I had to swap some code (0 to 1, 1 to 0) on your DH as it was backwards to the way that this is configured.

It’s late and I’m half asleep, but I think this post is what you’re looking for. Let me know if I’m missing anything that will be helpful to you. I have coding experience, but have yet to delve into Groovy (other than minor changes to your code). But, I’m happy to help any way I can.

Wow, pretty cool, thanks for sharing! Rewarding to see what crazy stuff people do with the project :slight_smile: lets me know I’m not wasting my time.

This makes a bit more sense. Set a pin to input and read it in. The only problem with your approach is the fact that you’re not pushing/switching the state of that switch in a timely manner. It will not update until something like a poll/refresh occurs. Make sure to look at Pollster which I use with all of my pull-type of devices.

Lastly, if you have a part number or ebay/amazon link to what you bought I may grab the same and attach it to the side door which is like a foot away from my project box.

You’re absolutely not wasting your time. This is a fantastic project, I’m definitely a fan.

I use CoRE as a “refresher” since I already make use of it for other rules. But, you’re correct, it does have to refresh in order to register the state change.

Here’s a link to a similar contact switch on Amazon. This is the same style as the one I have. But, any wired contact switch will do the trick.

Overhead Garage Door Floor Alarm System Switch Contact Sensor Track Mounted https://www.amazon.com/dp/B01KY938HS/ref=cm_sw_r_cp_api_OJaMybDVNK2Q5

That’s a nice sensor but I don’t see voltage requirements. Obviously it’s working for you so we’ll guess 3.3 or 5V is the rating. The cheap ones on eBay are for alarms and have a rating of 100VDC which is pretty crazy.

BTW, share your groovy code and I may just adjust your code to have the contact sensor capability I was talking about. Probably fastest you’ll get anything until I get something in myself. If you have any suggestions on some cheap door sensor, please recommend. Not interested in the same one as you have as it’s pretty much for the sliding garage door. My need is just a simple exterior door. Found cheap Arduino ones but they’re breakout boards with fancy variable resistors and DuPont/breadboard pins and not really made for installation but rather for prototyping.

That is crazy, considering most alarm panels are 24vdc. The contact I’m using is a standard contact for an alarm system. I picked it up at my local alarm supply distributor for $12.

Here’s the best $$value I’ve found, but they are intended to be recessed into the door and door frame respectively. But, for testing you could let the magnets hold themselves together and disconnect manually for the same effect.

I’ll post my code when I get on my PC in the AM.

NO Recessed Security Magnetic Door Window Contact Reed Switch 2 Pcs https://www.amazon.com/dp/B00O9YT3VU/ref=cm_sw_r_cp_api_GdbMybAZQR6EX

Yep I saw those too on eBay for $1 each… I found this one which folks mentioned Arduino works with MC-38 model which I bought on eBay from a US seller.

Keep in mind that 2 pin vs. 3 pin will have a totally different operation. 3 pin will simply require VCC & GND to be connected and the third pin sends a 0 or 1. While 2 pin models will depend on whether you connect one of the pins to VCC or GND that’s and your Pi/Arduino input pin defaults to 0 and when magnet is sensed it sends 1 for instance (or vice-versa depending on whether VCC or GND was connected to 1 of the 2 pins). Moral of the story, no all Arduino breakout boards with magnetic sensors will get me to exactly what you’re trying to do because there are a two ways of doing magnet sensing, 2 pin and 3 pin.

UPDATE: bought the 3 pin version too for prototyping :slight_smile: but it’s from China so will take a month.

Interesting. I have never seen a 3 pin contact, everything I run into is 2 pin. Can’t beat the pricing on those though!

Here is my DH

/**
 *  Generic HTTP Device v1.0.20161223
 *  Source code can be found here: https://github.com/JZ-SmartThings/SmartThings/blob/master/Devices/Generic%20HTTP%20Device/GenericHTTPDevice.groovy
 *  Copyright 2016 JZ
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 */

import groovy.json.JsonSlurper

metadata {
	definition (name: "Generic HTTP Device", author: "JZ", namespace:"JZ") {
		capability "Switch"
		capability "Temperature Measurement"
		capability "Polling"
		capability "Refresh"
		attribute "mainTriggered", "string"
		attribute "refreshTriggered", "string"
		attribute "customTriggered", "string"
		attribute "cpuUsage", "string"
		attribute "spaceUsed", "string"
		attribute "upTime", "string"
		attribute "cpuTemp", "string"
		attribute "freeMem", "string"
		attribute "temperature", "string"
		attribute "humidity", "string"
		command "DeviceTrigger"
		command "RefreshTrigger"
		command "CustomTrigger"
		command "RebootNow"
		command "ResetTiles"
		command "ClearTiles"
	}

	preferences {
		input("DeviceIP", "string", title:"Device IP Address", description: "Please enter your device's IP Address", required: true, displayDuringSetup: true)
		input("DevicePort", "string", title:"Device Port", description: "Empty assumes port 80.", required: false, displayDuringSetup: true)
		input("DevicePath", "string", title:"URL Path", description: "Rest of the URL, include forward slash.", displayDuringSetup: true)
		input(name: "DevicePostGet", type: "enum", title: "POST or GET", options: ["POST","GET"], defaultValue: "POST", required: false, displayDuringSetup: true)
		input("UseOffVoiceCommandForCustom", "bool", title:"Use the OFF voice command (e.g. by Alexa) to control the Custom command? Assumed ON if MainTrigger is Momentary setting below is ON.", description: "", defaultValue: false, required: false, displayDuringSetup: true)
		input("DeviceMainMomentary", "bool", title:"MainTrigger is Momentary?", description: "", defaultValue: true, required: false, displayDuringSetup: true)	
		input("DeviceMainPin", "number", title:'Main Pin Number in BCM Format', description: 'Empty assumes pin #4.', required: false, displayDuringSetup: false)
		input("DeviceCustomMomentary", "bool", title:"CustomTrigger is Momentary?", description: "", defaultValue: true, required: false, displayDuringSetup: true)
		input("DeviceCustomPin", "number", title:'Custom Pin Number in BCM Format', description: 'Empty assumes pin #21.', required: false, displayDuringSetup: false)
		input("UseJSON", "bool", title:"Use JSON instead of HTML?", description: "", defaultValue: false, required: false, displayDuringSetup: true)
		section() {
			input("HTTPAuth", "bool", title:"Requires User Auth?", description: "Choose if the HTTP requires basic authentication", defaultValue: false, required: true, displayDuringSetup: true)
			input("HTTPUser", "string", title:"HTTP User", description: "Enter your basic username", required: false, displayDuringSetup: true)
			input("HTTPPassword", "string", title:"HTTP Password", description: "Enter your basic password", required: false, displayDuringSetup: true)
		}
	}
	
	simulator {
	}

	tiles(scale: 2) {
		valueTile("displayName", "device.displayName", width: 6, height: 1, decoration: "flat") {
			state("default", label: '${currentValue}', backgroundColor:"#DDDDDD")
		}
		valueTile("mainTriggered", "device.mainTriggered", width: 5, height: 1, decoration: "flat") {
			state("default", label: 'Garage Door Opener:\r\n${currentValue}', backgroundColor:"#ffffff")
		}
		standardTile("DeviceTrigger", "device.switch", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true, decoration: "flat") {
			state "off", label:'OFF' , action: "on", icon: "st.Transportation.transportation14", backgroundColor:"#53a7c0", nextState: "trying"
			state "on", label: 'ON', action: "on", icon: "st.Transportation.transportation14", backgroundColor: "#FF6600", nextState: "trying"
			state "trying", label: 'TRYING', action: "ResetTiles", icon: "st.Transportation.transportation14", backgroundColor: "#FFAA33"
		}
		valueTile("customTriggered", "device.customTriggered", width: 5, height: 1, decoration: "flat") {
			state("default", label: 'Garage State:\r\n${currentValue}', backgroundColor:"#ffffff")
		}
		standardTile("CustomTrigger", "device.customswitch", width: 1, height: 1, decoration: "flat") {
			state "off", label:'Open', action: "CustomTrigger", icon: "st.Transportation.transportation13", backgroundColor:"#FF6600", nextState: "trying"
			state "on", label: 'Closed', action: "CustomTrigger", icon: "st.Transportation.transportation14", backgroundColor: "#53a7c0", nextState: "trying"
			state "trying", label: 'TRYING', action: "ResetTiles", icon: "st.Transportation.transportation12", backgroundColor: "#FFAA33"
		}
		valueTile("refreshTriggered", "device.refreshTriggered", width: 5, height: 1, decoration: "flat") {
			state("default", label: 'Refreshed:\r\n${currentValue}', backgroundColor:"#ffffff")
		}
		standardTile("RefreshTrigger", "device.refreshswitch", width: 1, height: 1, decoration: "flat") {
			state "default", label:'REFRESH', action: "refresh.refresh", icon: "st.secondary.refresh-icon", backgroundColor:"#53a7c0", nextState: "refreshing"
			state "refreshing", label: 'REFRESHING', action: "ResetTiles", icon: "st.secondary.refresh-icon", backgroundColor: "#FF6600", nextState: "default"
		}

		valueTile("cpuUsage", "device.cpuUsage", width: 2, height: 2) {
			state("default", label: 'CPU\r\n ${currentValue}%',
				backgroundColors:[
					[value: 0, color: "#00cc33"],
					[value: 10, color: "#99ff33"],
					[value: 30, color: "#ffcc99"],
					[value: 55, color: "#ff6600"],
					[value: 90, color: "#ff0000"]
				]
			)
		}
		valueTile("cpuTemp", "device.cpuTemp", width: 2, height: 2) {
			state("default", label: 'CPU Temp ${currentValue}',
				backgroundColors:[
					[value: 50, color: "#00cc33"],
					[value: 60, color: "#99ff33"],
					[value: 67, color: "#ff6600"],
					[value: 75, color: "#ff0000"]
				]
			)
		}
		valueTile("spaceUsed", "device.spaceUsed", width: 2, height: 2) {
			state("default", label: 'Space Used\r\n ${currentValue}%',
				backgroundColors:[
					[value: 50, color: "#00cc33"],
					[value: 75, color: "#ffcc66"],
					[value: 85, color: "#ff6600"],
					[value: 95, color: "#ff0000"]
				]
			)
		}
		valueTile("upTime", "device.upTime", width: 2, height: 2, decoration: "flat") {
			state("default", label: 'UpTime\r\n ${currentValue}', backgroundColor:"#ffffff")
		}
		valueTile("freeMem", "device.freeMem", width: 2, height: 2, decoration: "flat") {
			state("default", label: 'Free Mem\r\n ${currentValue}', backgroundColor:"#ffffff")
		}
		standardTile("clearTiles", "device.clear", width: 2, height: 2, decoration: "flat") {
			state "default", label:'Clear Tiles', action:"ClearTiles", icon:"st.Bath.bath9"
		}
		valueTile("temperature", "device.temperature", width: 2, height: 2) {
			state("default", label:'Temp\n ${currentValue}',
				backgroundColors:[
					[value: 32, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 92, color: "#d04e00"],
					[value: 98, color: "#bc2323"]
				]
			)
		}
		valueTile("humidity", "device.humidity", width: 2, height: 2) {
			state("default", label: 'Humidity\n ${currentValue}',
				backgroundColors:[
					[value: 50, color: "#00cc33"],
					[value: 60, color: "#99ff33"],
					[value: 67, color: "#ff6600"],
					[value: 75, color: "#ff0000"]
				]
			)
		}
		standardTile("RebootNow", "device.rebootnow", width: 1, height: 1, decoration: "flat") {
			state "default", label:'REBOOT' , action: "RebootNow", icon: "st.Seasonal Winter.seasonal-winter-014", backgroundColor:"#ff0000", nextState: "rebooting"
			state "rebooting", label: 'REBOOTING', action: "ResetTiles", icon: "st.Office.office13", backgroundColor: "#FF6600", nextState: "default"
		}
		main "DeviceTrigger"
		details(["displayName","mainTriggered", "DeviceTrigger", "customTriggered", "CustomTrigger", "refreshTriggered", "RefreshTrigger", "cpuUsage", "cpuTemp", "upTime", "spaceUsed", "freeMem", "clearTiles", "temperature", "humidity" , "RebootNow"])
	}
}

def refresh() {
	def FullCommand = 'Refresh='
	if (DeviceMainPin) {FullCommand=FullCommand+"&MainPin="+DeviceMainPin} //else {FullCommand=FullCommand+"&MainPin=4"}
	if (DeviceCustomPin) {FullCommand=FullCommand+"&CustomPin="+DeviceCustomPin} //else {FullCommand=FullCommand+"&CustomPin=21"}
	if (UseJSON==true) { FullCommand=FullCommand+"&UseJSON=" }
	runCmd(FullCommand)
}
def poll() {
	refresh()
}
def on() {
	def FullCommand = ''
	if (DeviceMainMomentary==true) {
		FullCommand='MainTrigger='
	} else {
		if (device.currentState("switch")!=null && device.currentState("switch").getValue()=="off") { FullCommand='MainTriggerOn=' } else { FullCommand='MainTriggerOff=' }
	}
	if (DeviceMainPin) {FullCommand=FullCommand+"&MainPin="+DeviceMainPin} //else {FullCommand=FullCommand+"&MainPin=4"}
	if (DeviceCustomPin) {FullCommand=FullCommand+"&CustomPin="+DeviceCustomPin} //else {FullCommand=FullCommand+"&CustomPin=21"}
	if (UseJSON==true) {FullCommand=FullCommand+"&UseJSON="}
	runCmd(FullCommand)
}
def off() {
	if (DeviceMainMomentary==true || UseOffVoiceCommandForCustom==true) {
		CustomTrigger()
	} else {
		log.debug "Running ON() Function for MAIN Command Handling."
		on()
	}
}
def CustomTrigger() {
	//log.debug device.currentState("customswitch").getValue() + " === customswitch state"
	def FullCommand = ''
	if (DeviceCustomMomentary==true) {
		FullCommand='CustomTrigger='
	} else {
		log.debug "main swtich currentState===" + device.currentState("switch")
		if (device.currentState("switch")!=null && device.currentState("customswitch").getValue()=="off") { FullCommand='CustomTriggerOn=' } else { FullCommand='CustomTriggerOff=' }
	}
	if (DeviceMainPin) {FullCommand=FullCommand+"&MainPin="+DeviceMainPin} //else {FullCommand=FullCommand+"&MainPin=4"}
	if (DeviceCustomPin) {FullCommand=FullCommand+"&CustomPin="+DeviceCustomPin} //else {FullCommand=FullCommand+"&CustomPin=21"}
	if (UseJSON==true) {FullCommand=FullCommand+"&UseJSON="}
	runCmd(FullCommand)
}
def RebootNow() {
	log.debug "Reboot Triggered!!!"
	runCmd('RebootNow=')
}
def ClearTiles() {
	sendEvent(name: "mainTriggered", value: "", unit: "")
	sendEvent(name: "customTriggered", value: "", unit: "")
	sendEvent(name: "refreshTriggered", value: "", unit: "")
	sendEvent(name: "cpuUsage", value: "", unit: "")
	sendEvent(name: "cpuTemp", value: "", unit: "")
	sendEvent(name: "spaceUsed", value: "", unit: "")
	sendEvent(name: "upTime", value: "", unit: "")
	sendEvent(name: "freeMem", value: "", unit: "")
	sendEvent(name: "temperature", value: "", unit: "")
	sendEvent(name: "humidity", value: "", unit: "")
}
def ResetTiles() {
	//RETURN BUTTONS TO CORRECT STATE
	if (DeviceMainMomentary==true) {
		sendEvent(name: "switch", value: "off", isStateChange: true)
	}
	if (DeviceCustomMomentary==true) {
		sendEvent(name: "customswitch", value: "off", isStateChange: true)
	}
	sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
	sendEvent(name: "rebootnow", value: "default", isStateChange: true)
	log.debug "Resetting tiles."
}

def runCmd(String varCommand) {
	def host = DeviceIP
	def hosthex = convertIPtoHex(host).toUpperCase()
	def LocalDevicePort = ''
	if (DevicePort==null) { LocalDevicePort = "80" } else { LocalDevicePort = DevicePort }
	def porthex = convertPortToHex(LocalDevicePort).toUpperCase()
	device.deviceNetworkId = "$hosthex:$porthex"
	def userpassascii = "${HTTPUser}:${HTTPPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()

	log.debug "The device id configured is: $device.deviceNetworkId"

	def headers = [:] 
	headers.put("HOST", "$host:$LocalDevicePort")
	headers.put("Content-Type", "application/x-www-form-urlencoded")
	if (HTTPAuth) {
		headers.put("Authorization", userpass)
	}
	log.debug "The Header is $headers"

	def path = ''
	def body = ''
	log.debug "Uses which method: $DevicePostGet"
	def method = "POST"
	try {
		if (DevicePostGet.toUpperCase() == "GET") {
			method = "GET"
			path = varCommand
			if (path.substring(0,1) != "/") { path = "/" + path }
			log.debug "GET path is: $path"
		} else {
			path = DevicePath
			body = varCommand 
			log.debug "POST body is: $body"
		}
		log.debug "The method is $method"
	}
	catch (Exception e) {
		settings.DevicePostGet = "POST"
		log.debug e
		log.debug "You must not have set the preference for the DevicePOSTGET option"
	}

	try {
		def hubAction = new physicalgraph.device.HubAction(
			method: method,
			path: path,
			body: body,
			headers: headers
			)
		hubAction.options = [outputMsgToS3:false]
		log.debug hubAction
		hubAction
	}
	catch (Exception e) {
		log.debug "Hit Exception $e on $hubAction"
	}
}

def parse(String description) {
//	log.debug "Parsing '${description}'"
	def whichTile = ''
	def map = [:]
	def retResult = []
	def descMap = parseDescriptionAsMap(description)
	def jsonlist = [:]
	def bodyReturned = ' '
	def headersReturned = ' '
	if (descMap["body"] && descMap["headers"]) {
		bodyReturned = new String(descMap["body"].decodeBase64())
		headersReturned = new String(descMap["headers"].decodeBase64())
	}
	//log.debug "BODY---" + bodyReturned
	//log.debug "HEADERS---" + headersReturned

	if (descMap["body"]) {
		if (headersReturned.contains("application/json")) {
			def body = new String(descMap["body"].decodeBase64())
			def slurper = new JsonSlurper()
			jsonlist = slurper.parseText(body)
			//log.debug "JSONLIST---" + jsonlist."CPU"
			jsonlist.put ("Date", new Date().format("yyyy-MM-dd h:mm:ss a", location.timeZone))
		} else if (headersReturned.contains("text/html")) {
			jsonlist.put ("Date", new Date().format("yyyy-MM-dd h:mm:ss a", location.timeZone))
			def data=bodyReturned.eachLine { line ->
				if (line.contains('CPU=')) { jsonlist.put ("CPU", line.replace("CPU=","")) }
				if (line.contains('Space Used=')) { jsonlist.put ("Space Used", line.replace("Space Used=","")) }
				if (line.contains('UpTime=')) { jsonlist.put ("UpTime", line.replace("UpTime=","")) }
				if (line.contains('CPU Temp=')) { jsonlist.put ("CPU Temp",line.replace("CPU Temp=","")) }
				if (line.contains('Free Mem=')) { jsonlist.put ("Free Mem",line.replace("Free Mem=",""))  }
				if (line.contains('Temperature=')) { jsonlist.put ("Temperature",line.replace("Temperature=","").replaceAll("[^\\p{ASCII}]", "°")) }
				if (line.contains('Humidity=')) { jsonlist.put ("Humidity",line.replace("Humidity=","")) }
				if (line.contains('MainTrigger=Success')) { jsonlist.put ("MainTrigger".replace("=",""), "Success") }
				if (line.contains('MainTrigger=Failed : Authentication Required!')) { jsonlist.put ("MainTrigger".replace("=",""), "Authentication Required!") }
				if (line.contains('MainTriggerOn=Success')) { jsonlist.put ("MainTriggerOn", "Success") }
				if (line.contains('MainTriggerOn=Failed : Authentication Required!')) { jsonlist.put ("MainTriggerOn", "Authentication Required!") }
				if (line.contains('MainTriggerOff=Success')) { jsonlist.put ("MainTriggerOff", "Success") }
				if (line.contains('MainTriggerOff=Failed : Authentication Required!')) { jsonlist.put ("MainTriggerOff", "Authentication Required!") }
				if (line.contains('MainPinStatus=1')) { jsonlist.put ("MainPinStatus".replace("=",""), 1) }
				if (line.contains('MainPinStatus=0')) { jsonlist.put ("MainPinStatus".replace("=",""), 0) }
				if (line.contains('CustomTrigger=Success')) { jsonlist.put ("CustomTrigger", "Success") }
				if (line.contains('CustomTrigger=Failed : Authentication Required!')) { jsonlist.put ("CustomTrigger", "Authentication Required!") }
				if (line.contains('CustomTriggerOn=Success')) { jsonlist.put ("CustomTriggerOn", "Success") }
				if (line.contains('CustomTriggerOn=Failed : Authentication Required!')) { jsonlist.put ("CustomTriggerOn", "Authentication Required!") }
				if (line.contains('CustomTriggerOff=Success')) { jsonlist.put ("CustomTriggerOff", "Success") }
				if (line.contains('CustomTriggerOff=Failed : Authentication Required!')) { jsonlist.put ("CustomTriggerOff", "Authentication Required!") }
				if (line.contains('CustomPinStatus=1')) { jsonlist.put ("CustomPinStatus".replace("=",""), 1) }
				if (line.contains('CustomPinStatus=0')) { jsonlist.put ("CustomPinStatus".replace("=",""), 0) }
				if (line.contains('Refresh=Success')) { jsonlist.put ("Refresh", "Success") }
				if (line.contains('Refresh=Failed : Authentication Required!')) { jsonlist.put ("Refresh", "Authentication Required!") }
				if (line.contains('RebootNow=Success')) { jsonlist.put ("RebootNow", "Success") }
				if (line.contains('RebootNow=Failed : Authentication Required!')) { jsonlist.put ("RebootNow", "Authentication Required!") }
				//ARDUINO CHECKS
				if (line.contains('/MainTrigger=')) { jsonlist.put ("MainTrigger".replace("=",""), "Success") }
				if (line.contains('/MainTriggerOn=')) { jsonlist.put ("MainTriggerOn", "Success") }
				if (line.contains('/MainTriggerOff=')) { jsonlist.put ("MainTriggerOff", "Success") }
				if (line.contains('RELAY1 pin is now: On')) { jsonlist.put ("MainPinStatus".replace("=",""), 1) }
				if (line.contains('RELAY1 pin is now: Off')) { jsonlist.put ("MainPinStatus".replace("=",""), 0) }
				if (line.contains('/CustomTrigger=')) { jsonlist.put ("CustomTrigger".replace("=",""), "Success") }
				if (line.contains('/CustomTriggerOn=')) { jsonlist.put ("CustomTriggerOn", "Success") }
				if (line.contains('/CustomTriggerOff=')) { jsonlist.put ("CustomTriggerOff", "Success") }
				if (line.contains('RELAY2 pin is now: On')) { jsonlist.put ("CustomPinStatus".replace("=",""), 1) }
				if (line.contains('RELAY2 pin is now: Off')) { jsonlist.put ("CustomPinStatus".replace("=",""), 0) }
				if (line == '/Refresh=') { jsonlist.put ("Refresh", "Success") }
			}
		}
	}
	if (descMap["body"] && (headersReturned.contains("application/json") || headersReturned.contains("text/html"))) {
		//putImageInS3(descMap)
		if (jsonlist."Refresh"=="Authentication Required!") {
			sendEvent(name: "refreshTriggered", value: "Use Authentication Credentials", unit: "")
			whichTile = 'refresh'
		}
		if (jsonlist."Refresh"=="Success") {
			sendEvent(name: "refreshTriggered", value: jsonlist."Date", unit: "")
			whichTile = 'refresh'
		}
		if (jsonlist."CustomTrigger"=="Authentication Required!") {
			sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
		}
		if (jsonlist."CustomTrigger"=="Success") {
			sendEvent(name: "customswitch", value: "on", isStateChange: true)
			sendEvent(name: "customTriggered", value: "MOMENTARY @ " + jsonlist."Date", unit: "")
			whichTile = 'customoff'
		}
		if (jsonlist."CustomTriggerOn"=="Success" && jsonlist."CustomPinStatus"==1) {
			sendEvent(name: "customTriggered", value: "ON @ " + jsonlist."Date", unit: "")
			whichTile = 'customon'
		}
		if (jsonlist."CustomTriggerOn"=="Authentication Required!") {
			sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
		}
		if (jsonlist."CustomTriggerOff"=="Success" && jsonlist."CustomPinStatus"==0) {
			sendEvent(name: "customTriggered", value: "OFF @ " + jsonlist."Date", unit: "")
			whichTile = 'customoff'
		}
		if (jsonlist."CustomTriggerOff"=="Authentication Required!") {
			sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
		}
		if (jsonlist."CustomPinStatus"==1) {
			sendEvent(name: "customswitch", value: "on", isStateChange: true)
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			whichTile = 'customon'
		}
		else if (jsonlist."CustomPinStatus"==0) {
			sendEvent(name: "customswitch", value: "off", isStateChange: true)
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			whichTile = 'customoff'
		}
		if (jsonlist."MainTrigger"=="Authentication Required!") {
			sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
		}
		if (jsonlist."MainTrigger"=="Success") {
			sendEvent(name: "switch", value: "on", isStateChange: true)
			sendEvent(name: "mainTriggered", value: "MOMENTARY @ " + jsonlist."Date", unit: "")
			whichTile = 'mainoff'
		}
		if (jsonlist."MainTriggerOn"=="Success" && jsonlist."MainPinStatus"==1) {
			sendEvent(name: "mainTriggered", value: "ON @ " + jsonlist."Date", unit: "")
			whichTile = 'mainon'
		}
		if (jsonlist."MainTriggerOn"=="Authentication Required!") {
			sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
		}
		if (jsonlist."MainTriggerOff"=="Success" && jsonlist."MainPinStatus"==0) {
			sendEvent(name: "mainTriggered", value: "OFF @ " + jsonlist."Date", unit: "")
			whichTile = 'mainoff'
		}
		if (jsonlist."MainTriggerOff"=="Authentication Required!") {
			sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
			whichTile = 'mainoff'
		}
		if (jsonlist."MainPinStatus"==1) {
			sendEvent(name: "switch", value: "on", isStateChange: true)
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			whichTile = 'mainon'
		}
		else if (jsonlist."MainPinStatus"==0) {
			sendEvent(name: "switch", value: "off", isStateChange: true)
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			whichTile = 'mainoff'
		}
		if (jsonlist."CPU") {
			sendEvent(name: "cpuUsage", value: jsonlist."CPU".replace("=","\n").replace("%",""), unit: "")
		}
		if (jsonlist."Space Used") {
			sendEvent(name: "spaceUsed", value: jsonlist."Space Used".replace("=","\n").replace("%",""), unit: "")
		}
		if (jsonlist."UpTime") {
			sendEvent(name: "upTime", value: jsonlist."UpTime".replace("=","\n"), unit: "")
		}
		if (jsonlist."CPU Temp") {
			sendEvent(name: "cpuTemp", value: jsonlist."CPU Temp".replace("=","\n").replace("\'","°").replace("C ","C="), unit: "")
		}
		if (jsonlist."Free Mem") {
			sendEvent(name: "freeMem", value: jsonlist."Free Mem".replace("=","\n"), unit: "")
		}
		if (jsonlist."Temperature") {
			sendEvent(name: "temperature", value: jsonlist."Temperature".replace("=","\n").replace("\'","°").replace("C ","C="), unit: "")
			//String s = jsonlist."Temperature"
			//for(int i = 0; i < s.length(); i++)	{
			//   int c = s.charAt(i);
			//   log.trace "'${c}'\n"
			//}
		}
		if (jsonlist."Humidity") {
			sendEvent(name: "humidity", value: jsonlist."Humidity".replace("=","\n"), unit: "")
		}
		if (jsonlist."RebootNow") {
			whichTile = 'RebootNow'
		}
	}

	log.debug jsonlist

	//RESET THE DEVICE ID TO GENERIC/RANDOM NUMBER. THIS ALLOWS MULTIPLE DEVICES TO USE THE SAME ID/IP
	device.deviceNetworkId = "ID_WILL_BE_CHANGED_AT_RUNTIME_" + (Math.abs(new Random().nextInt()) % 99999 + 1)

	//CHANGE NAME TILE
	sendEvent(name: "displayName", value: DeviceIP, unit: "")

	//RETURN BUTTONS TO CORRECT STATE
	log.debug 'whichTile: ' + whichTile
    switch (whichTile) {
        case 'refresh':
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			def result = createEvent(name: "refreshswitch", value: "default", isStateChange: true)
			//log.debug "refreshswitch returned ${result?.descriptionText}"
			return result
        case 'customoff':
			sendEvent(name: "customswitch", value: "off", isStateChange: true)
			def result = createEvent(name: "customswitch", value: "off", isStateChange: true)
			return result
        case 'customon':
			sendEvent(name: "customswitch", value: "on", isStateChange: true)
			def result = createEvent(name: "customswitch", value: "on", isStateChange: true)
			return result
        case 'mainoff':
			def result = createEvent(name: "switch", value: "off", isStateChange: true)
			return result
        case 'mainon':
			def result = createEvent(name: "switch", value: "on", isStateChange: true)
			return result
        case 'RebootNow':
			sendEvent(name: "rebootnow", value: "default", isStateChange: true)
			def result = createEvent(name: "rebootnow", value: "default", isStateChange: true)
			return result
        default:
			sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
			def result = createEvent(name: "refreshswitch", value: "default", isStateChange: true)
			//log.debug "refreshswitch returned ${result?.descriptionText}"
			return result
    }
}

def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
	def nameAndValue = param.split(":")
	map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
}
private String convertIPtoHex(ipAddress) { 
	String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
	//log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
	return hex
}
private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
	//log.debug hexport
	return hexport
}
private Integer convertHexToInt(hex) {
	Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
	//log.debug("Convert hex to ip: $hex") 
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
	def parts = device.deviceNetworkId.split(":")
	//log.debug device.deviceNetworkId
	def ip = convertHexToIP(parts[0])
	def port = convertHexToInt(parts[1])
	return ip + ":" + port
}