A Tiny Webserver I use for icons and logging for my Hubitat Controlled ERV project

I use this for logging from inside a rule, using an HTTP GET, for saving data from Maker API to disk on a Windows host.

I will probably add emailing to this app soon so I can send the files as attachments to IFTTT and have them save the attachment to Google drive, I imagine.

Here is the rule

C# Code Here
Heres the Project Blog

And the possibility of using the maker API to run real-time graphs and gauges like in Node-Red.

I'm working on a wee project building an ERV system using my Hubitat, Arduino and Xiaomi.

Usage

HTTP call in rule machine

http://192.168.1.71:82/logtempertures.svs?csv=Outside%20Climate,Hallway%20Climate,Bedroom%201,Bedroom%202,Bathroom%20Climate,Room%20Climate,Office%20Climate

You can see how the above URL works ?csv=name of device 1, name of device 2

?csv=name of device 1, name of device 2

Makes the logger log a file on the Windows host for the whole day, and return all the rows currently in the file.

?json=name of device 1, name of device 2

Makes a log for the yyyy-MM-dd-hh-mm-ss

The file name is logNAME-yyyy-MM-dd.txtx for CSV data and logNAME-yyyy-MM-dd-hh-mm-ss.json for the JSON format.

As long as the call begins with log and has the .svs extension it will work.

if you use http://192.168.1.71:82/logtempertures.svs?json=Outside%20Climate, for example, you get back JSON

The JSON with the current device value and is stored here, a new file for every second.

You need to set the Get All Devices URL e.g. http://192.168.1.65/apps/api/6/devices?access_token=???????????????????? to point to your Hub in App.Config

A JavaScript usage example

	$.getJSON('http://192.168.1.71:82/logtempertures.svs?json=Outside%20Climate', function(json){
	
		console.log(json[0].attributes)
	
		var val = json[0].attributes[6].currentValue;

		console.log(json[0].val)
	
	});

The zip file has a few JavaScipt examples

The C# that does the work

     static void HTTPserver_SVSrequest(object o, HTTPrequest Request)
    {

        if (Request.Action != null)
            Console.WriteLine("Action: " + Request.Action.Trim());

        if (Request.Args != null)
            Console.WriteLine("Post Args: " + Request.Args);

        if (Request.Action.Trim().ToLower().EndsWith(".svs"))
        {
            if (Request.Action.ToLower().StartsWith("log"))
            {
                JArray deviceJSON = new JArray();

                //Request.RawURL
                //"/LogClimate.svs?ids=Hallway%20Climate,Outside%20Climate"

                string args = "";

                if (Request.Method.Trim() == "POST")
                    args = Request.Args.TrimEnd('#').Replace("%20", " ");
                else if (Request.Method.Trim() == "GET")
                    args = Request.RawURL.Replace("%20", " ");

                string[] dlabs = args.Split('=')[1].Split(',');

                using (WebClient wc = new WebClient())
                {
                    string url = Settings.Default.GetAllDevicesURL;

                    string sjson = wc.DownloadString(url);

                    DeviceList = JArray.Parse(sjson);

                }

                string CSVline = DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss") + ",";

                //Each ID
                foreach (string dlab in dlabs)
                {
                    //Find Device
                    foreach (JObject jo in DeviceList)
                    {                            
                        /*
                        {
                        "id":"673",
                        "name":"Xiaomi Temperature Humidity Sensor",
                        "label":"Hallway Climate"
                        },
                        */
                        if (jo["label"].ToString() == dlab)
                        {
                            string did = jo["id"].ToString();

                            CSVline += ("device," + dlab + ",ld," + did + ",");

                            using (WebClient wc = new WebClient())
                            {                                 
                                //e.g. http://192.168.1.65/apps/api/6/devices/673?access_token=ae6f8845-6916-4b8a-a9b2-67f7f5261ef7
                                string url = string.Format("http://192.168.1.65/apps/api/6/devices/{0}?access_token=ae6f8845-6916-4b8a-a9b2-67f7f5261ef7", did);
                                string sjson = wc.DownloadString(url);

                                JObject jod = JObject.Parse(sjson);

                                deviceJSON.Add(jod);
                                /*
                                "attributes":[
                                    {
                                    "name":"battery",
                                    "currentValue":70,
                                    "dataType":"NUMBER"
                                    },
                                    {
                                    "name":"batteryLastReplaced",
                                    "currentValue":"Jul 20 2020",
                                    "dataType":"STRING"
                                    },
                                    {
                                    "name":"humidity",
                                    "currentValue":31.9,
                                    "dataType":"NUMBER"
                                    },
                                    {
                                    "name":"lastCheckinEpoch",
                                    "currentValue":"1601591927215",
                                    "dataType":"STRING"
                                    },
                                    {
                                    "name":"lastCheckinTime",
                                    "currentValue":"Oct 2, 2020 11:38:47 AM",
                                    "dataType":"DATE"
                                    },
                                    {
                                    "name":"pressure",
                                    "currentValue":null,
                                    "dataType":"NUMBER"
                                    },
                                    {
                                    "name":"temperature",
                                    "currentValue":23.79,
                                    "dataType":"NUMBER"
                                    }
                                ],
                                */
                                string rejectAttr = "lastCheckinTime,lastCheckinEpoch,batteryLastReplaced";

                                JArray attrs = JArray.Parse(jod["attributes"].ToString());

                                foreach(JObject attr in attrs)
                                    if(!rejectAttr.Contains(attr["name"].ToString()) )
                                        CSVline += (attr["name"].ToString() + "," +  attr["currentValue"].ToString() + ",");
                                
                            }//End of using (WebClient wc = new WebClient())

                            break;//Whole device done

                        }//End of if (jo["label"].ToString() == dlab)

                    }//End of foreach (JObject jo in DeviceList)

                }//End of  foreach (string dlab in dlabs)

                CSVline = CSVline.TrimEnd(',');

                //WRITE AS JSON
                if (args.Contains("json"))
                {
                    string folder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\json\";

                    if (!Directory.Exists(folder))
                        Directory.CreateDirectory(folder);

                    string file = folder + Request.Action.Replace("/", "\\") + "-" + DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss") + ".json";

                    string sjson = JsonConvert.SerializeObject(deviceJSON);

                    File.WriteAllText(file, sjson);
                    
                    //Send back last value usefull
                    HTTPserver.Response(sjson);
                    
                }
                //WRITE AS CSV TEXT
                else if (args.Contains("csv"))                    
                {
                    string folder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\data\";

                    if (!Directory.Exists(folder))
                        Directory.CreateDirectory(folder);

                    string file = folder + Request.Action.Replace("/", "\\") + "-" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt";

                    //Add this line
                    File.AppendAllText(file, CSVline + "\r\n");

                   //Get whole file so far
                    string CSVlines = File.ReadAllText(file);

                    HTTPserver.Response(CSVlines);
                } 

            }//End of if (Request.Action.ToLower().StartsWith("logclimate"))

        }//End of else if (Request.Action.Trim().ToLower().EndsWith(".svs"))
    }

Hubitat I may be missing something, but if not, when are you going to add some JSON handling into your marvellous product, please?

1 Like

Huh? What do you mean? The Groovy language has built in support for json parsing and writing.

1 Like

Can I use Groovy in a Rule?

I'd like to be able to be able to use JSON in dashboards and Rules a bit, maybe an JSON variable type?

Node-Red was good at that

Arduino code

#include <Arduino_JSON.h>
#include <ESP8266WiFi.h>
#include <MQTT.h>

#define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED

#include "DHT.h"
// Uncomment whatever type you're using!
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

WiFiClient net;
MQTTClient MQTTclient; 


const char ssid[] = "Network";
const char pass[] = "#$%^&";

// the IP address for the MQTT server
char MQTTip[] = "192.168.1.71";   

unsigned long UpdateCount = 12;
unsigned long TickCount = UpdateCount-1;
unsigned long PacketCount = 0;
unsigned long lastMillis = 0;

DHT dht[4] = {DHT(4, DHTTYPE),DHT(0, DHTTYPE),DHT(2, DHTTYPE),DHT(14, DHTTYPE)};

DHT loft(12, DHT11);

void setup() {

  pinMode(LED_PIN, OUTPUT);
  
  Serial.begin(115200);

  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  MQTTclient.begin(MQTTip, net);
  MQTTclient.onMessage(messageReceived);

  WiFiconnect();


  MQTTclient.publish("/ervdas/status", "start");

  for(int  i = 0;i < 4;i++)
  {
	dht[i].begin();
  }

  loft.begin();

  MQTTclient.publish("/ervdas/status", "run");
}

void loop() {
  
  MQTTclient.loop();
  
  delay(10);  // <- fixes some issues with WiFi stability

  if (!MQTTclient.connected()) {
	WiFiconnect();
  }

	
  // publish a message roughly every second.
  if (millis() - lastMillis > (UpdateCount * 1000)) 
  { 
	 lastMillis = millis();
	 SendMQTT();
  }
  
  delay(100);

}

void WiFiconnect() {
  
  Serial.print("checking wifi...");
  Serial.println(ssid);
  
  while (WiFi.status() != WL_CONNECTED) {
	Serial.print(".");
	delay(500);
	digitalWrite(LED_PIN, LOW);
	delay(500);
	digitalWrite(LED_PIN, HIGH);
  }
  
  Serial.println("\nWiFi Connected! ");
  Serial.println( WiFi.localIP());

  Serial.print("\nMQTT connecting...");
  while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) {
	Serial.print(".");
	delay(800);
	digitalWrite(LED_PIN, LOW);
	delay(200);
	digitalWrite(LED_PIN, HIGH);
  }

  Serial.println("\nMQTT Connected!");

  digitalWrite(LED_PIN, LOW);
  
  MQTTclient.subscribe("/ervdas/control");  
  MQTTclient.publish("/ervdas/status", "start");
  MQTTclient.publish("/ervdas/localip", "/ervdas/localip/" + WiFi.localIP().toString());

}

void SendMQTT()
{
  digitalWrite(LED_PIN, LOW);
	
  JSONVar myArray;

  //STALE
  myArray[0] = (!isnan(dht[0].readTemperature()) ? dht[0].readTemperature() : 999);
  myArray[1] = (!isnan(dht[0].readHumidity()) ? dht[0].readHumidity() : 999);

  //FRESH
  myArray[2] = (!isnan(dht[1].readTemperature()) ? dht[1].readTemperature() : 999);
  myArray[3] = (!isnan(dht[1].readHumidity()) ? dht[1].readHumidity() : 999);


  //INLET
  myArray[4] = (!isnan(dht[2].readTemperature()) ? dht[2].readTemperature() : 999);
  myArray[5] = (!isnan(dht[2].readHumidity()) ? dht[2].readHumidity() : 999);
 

  //EXHAUST
  myArray[6] = (!isnan(dht[3].readTemperature()) ? dht[3].readTemperature() : 999);
  myArray[7] = (!isnan(dht[3].readHumidity()) ? dht[3].readHumidity() : 999);

  
  //LOFT
  myArray[8] = (!isnan(loft.readTemperature()) ? loft.readTemperature() : 999);
  myArray[9] = (!isnan(loft.readHumidity()) ? loft.readHumidity() : 999); 
  
  //MISC
  myArray[10] = (PacketCount++);
  myArray[11] = WiFi.RSSI();
  myArray[12] = analogRead(0);

   
  String jsonString = JSON.stringify(myArray);
  
  MQTTclient.publish("/ervdas/sensors", jsonString);
  Serial.print("/ervdas/sensors " );
  Serial.print(jsonString );
  
  Serial.println();

  digitalWrite(LED_PIN, HIGH);
}

float toFixed(float f, int dp)
{
  char buf[40] = {0, };
  sprintf(buf, "%03d", f);
  return atof(buf);
}

void messageReceived(String &topic, String &payload) {

  digitalWrite(LED_PIN, LOW);
  
  Serial.println("incoming: " + topic + " - " + payload);
  
  if(topic == "/ervdas/control")
  {  
	if(payload.startsWith("UpdateCount"))
	{
	  UpdateCount = (long)payload.substring(12).toInt();
	  TickCount = UpdateCount -1;
	}
	else if(payload.startsWith("read"))
	{
	  SendMQTT();
	   MQTTclient.publish("/ervdas/localip", "/ervdas/localip/" + WiFi.localIP().toString());
	}
  
	MQTTclient.publish("/ervdas/payload", topic + "/" + payload);

  }

  digitalWrite(LED_PIN, HIGH);
 
}

Device driver

metadata {
	definition(name: "Greenway MQTT ERV DAS Driver", namespace: "Greenway", author: "Nick Goodey") {
		capability "Initialize"
		capability "Sensor"
		capability "Polling"
		capability "Battery"


		//ERV DAS

		attribute "StaleTile", "String"
		attribute "StaleTemp", "Number"
		attribute "StaleHumidity", "Number"

		attribute "FreshTile", "String"
		attribute "FreshTemp", "Number"
		attribute "FreshHumidity",  "Number"

		attribute "InletTile", "String"
		attribute "InletTemp", "Number"
		attribute "InletHumidity", "Number"

		attribute "ExhaustTile", "String"
		attribute "ExhaustTemp", "Number"
		attribute "ExhaustHumidity",  "Number"

		attribute "LoftTile", "String"
		attribute "LoftTemp", "Number"
		attribute "LoftHumidity",  "Number"
		
		attribute "Packet",  "Number"
		attribute "RSSI",  "Number"
		attribute "Analog",  "Number"
		
		attribute "ExhaustStaleTile", "String"
		attribute "ExhaustStaleTemp", "Number"
		attribute "ExhaustStaleHumidity",  "Number"

		attribute "FreshInletTile", "String"
		attribute "FreshInletTemp", "Number"
		attribute "FreshInletHumidity",  "Number"


	}

	preferences {


		input name: "MQTTBroker", type: "text", title: "MQTT Broker Address:", required: true, displayDuringSetup: true
		input name: "username", type: "text", title: "MQTT Username:", description: "(blank if none)", required: false, displayDuringSetup: true
		input name: "password", type: "password", title: "MQTT Password:", description: "(blank if none)", required: false, displayDuringSetup: true
		input name: "topicSub", type: "text", title: "Topic to Subscribe:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
		input name: "topicCon", type: "text", title: "Topic to control:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
		input name: "topicPoll", type: "text", title: "Topic to poll device:", description: "Example Topic (topic/device/#)", required: false, displayDuringSetup: true
   
		input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)
		

	}

}


def installed() {
	log.info "installed..."
}



def poll() {

	 displayDebugLog("poll")
	
	try {
		topic = settings?.topicPoll
		if (logEnable) log.debug "Poll: $topic/read"
		interfaces.mqtt.publish(topic, "read", 1, false)

	} catch (e) {
		log.error "Device Poll error: ${e.message}"
	}

}

// Parse incoming device messages to generate events
def parse(String description) {


	try {

		displayDebugLog("Parse")
		displayDebugLog(description);
		displayDebugLog(did);
		
		did = device.getId()

		state.DeviceID = did
		
		// parse message
		mqtt = interfaces.mqtt.parseMessage(description)

		if (logEnable) log.debug mqtt.topic
		if (logEnable) log.debug mqtt.payload

		state.topic = mqtt.topic

		json = new groovy.json.JsonSlurper().parseText(mqtt.payload)
			  
		/* 
		  //STALE
		  myArray[0] = dht[0].readTemperature();
		  myArray[1] = dht[0].readHumidity(); 

		  //FRESH
		  myArray[2] = dht[1].readTemperature();
		  myArray[3] = dht[1].readHumidity(); 

		  //INLET
		  myArray[4] = dht[2].readTemperature();
		  myArray[5] = dht[2].readHumidity(); 

		  //EXHAUST
		  myArray[6] = dht[3].readTemperature();
		  myArray[7] = dht[3].readHumidity(); 
		  
		  //LOFT
		  myArray[8] = loft.readTemperature();
		  myArray[9] = loft.readHumidity(); 

		  //MISC
		  myArray[9] = (PacketCount++);
		  myArray[10] = WiFi.RSSI();
		  myArray[11] = analogRead(0);
		*/
		//Double.parseDouble(
		
		state.StaleTemp = (float)(json[0] * 100)/100.0
		state.StaleHumidity = (float)(json[1] * 100)/100.0
		sendEvent(name: "StaleTemp", value: state.StaleTemp)        
		sendEvent(name: "StaleHumidity", value: state.StaleHumidity)
		sendEvent(name: "StaleTile", value: "Stale Air<br>$state.StaleTemp °C<BR>$state.StaleHumidity %")

		state.FreshTemp = (float)(json[2] * 100)/100.0
		state.FreshHumidity = (float)(json[3] * 100)/100.0
		sendEvent(name: "FreshTemp", value: state.FreshTemp)
		sendEvent(name: "FreshHumidity", value: state.FreshHumidity)
		sendEvent(name: "FreshTile", value: "Fresh Air<br>$state.FreshTemp °C<BR>$state.FreshHumidity %")

		state.InletTemp = (float)(json[4] * 100)/100.0
		state.InletHumidity = (float)(json[5] * 100)/100.0
		sendEvent(name: "InletTemp", value: state.InletTemp)
		sendEvent(name: "InletHumidity", value: state.InletHumidity)
		sendEvent(name: "InletTile", value: "Intake Air<br>$state.InletTemp °C<BR>$state.InletHumidity %")
 
		state.ExhaustTemp = (float)(json[6] * 100)/100.0
		state.ExhaustHumidity = (float)(json[7] * 100)/100.0
		sendEvent(name: "ExhaustTemp", value: state.ExhaustTemp)
		sendEvent(name: "ExhaustHumidity", value: state.ExhaustHumidity)
		sendEvent(name: "ExhaustTile", value: "Exhaust Air<br>$state.ExhaustTemp °C<BR>$state.ExhaustHumidity %")
		
		state.LoftTemp = (float)(json[8] * 100)/100.0
		state.LoftHumidity = (float)(json[9] * 100)/100.0
		sendEvent(name: "LoftTemp", value: state.ExhaustTemp)
		sendEvent(name: "LoftHumidity", value: state.ExhaustHumidity)
		sendEvent(name: "LoftTile", value: "Loft Air<br>$state.LoftTemp °C<BR>$state.LoftHumidity %")
  
		
		sendEvent(name: "Packet", value: json[10])        
		sendEvent(name: "RSSI", value: json[11])
		sendEvent(name: "Analog", value: json[12])
		
		state.ExhaustStaleTemp = (float)( (state.StaleTemp - state.ExhaustTemp) * 1000)/1000.0 
		state.ExhaustStaleHumidity = (float)( (state.StaleHumidity - state.ExhaustHumidity) * 100)/100.0         
		sendEvent(name: "ExhaustStaleTemp", value: state.ExhaustStaleTemp)
		sendEvent(name: "ExhaustStaleHumidity", value: state.ExhaustStaleHumidity)
		sendEvent(name: "ExhaustStaleTile", value: "Exhaust - Stale Air<br>$state.ExhaustStaleTemp °C<BR>$state.ExhaustStaleHumidity %")
		
		state.FreshInletTemp = (float)( (state.FreshTemp - state.InletTemp) * 1000)/1000.0 
		state.FreshInletHumidity = (float)( (state.StaleHumidity - state.ExhaustHumidity) * 100)/100.0        
		sendEvent(name: "FreshInletTemp", value: state.FreshInletTemp)
		sendEvent(name: "FreshInletHumidity", value: state.FreshInletHumidity)
		sendEvent(name: "FreshInletTile", value: "Fresh - Inlet Air<br>$state.FreshInletTemp °C<BR>$state.FreshInletHumidity %")
		
		

	}
	catch (Exception e) 
	{
		log.error "Parse error: ${e.message}"
	}

}


def updated() {
	if (logEnable) log.info "Updated..."
	initialize()
}

def uninstalled() {
	if (logEnable) log.info "Disconnecting from mqtt"
	interfaces.mqtt.disconnect()
}


def initialize() {
	
	
	displayDebugLog("initialize")

	if (logEnable) runIn(900, logsOff)

	state.ThisCharge = 0
	state.ChargeLimit = 0
	state.Relay = -1

	MQTTconnect()
	
	unschedule()
	
	schedule("0/10 * * * * ? *", MQTTconnect)
	

}


def MQTTconnect() {

	try {

		def mqttInt = interfaces.mqtt

		if (mqttInt.isConnected()) {
			displayDebugLog( "Allready Connected to: $MQTTBroker $topicSub")
			return
		}

		def clientID = "hubitat-" + device.deviceNetworkId
		state.clientID = clientID
		
		 displayDebugLog( "Allready Connected to: $MQTTBroker $settings?.MQTTBroker/$settings?.topicSub")

		//open connection
		mqttbroker = "tcp://" + settings?.MQTTBroker + ":1883"
		mqttInt.connect(mqttbroker, clientID, settings?.username, settings?.password)

		//give it a chance to start
		pauseExecution(500)

		mqttInt.subscribe(settings?.topicSub)

		log.info "Connection established: $MQTTBroker $topicSub"
		log.info "clientID: $clientID"
		log.info "Subscribed to: $topicSub"


	} catch (e) {
		log.error "MQTTconnect error: ${e.message}"
	}
}


def mqttClientStatus(String status) {
	log.error "MQTTStatus- error: ${status}"
}

def logsOff() {
	log.warn "Debug logging disabled."
	device.updateSetting("logEnable", [value: "false", type: "bool"])
}




private def displayDebugLog(message) {
	if (logEnable) log.debug "${device.displayName}: ${message}"
}

private def displayInfoLog(message) {

	log.info "${device.displayName}: ${message}"
}

Not sure who I cribbed the above off originally I think it was a community driver

Driver for ERV Fans Working

Size fans PWM control as well as start relays.

Four boost fans in the ducts for each room inlet.

One fan in the two way turbo exhaust valve

Above the pretty simple circuit to start the fans and set fan speeds from Hubitat.

Hubitat Groovy Driver

import groovy.json.JsonSlurper

metadata {
	definition(name: "Greenway ERV Fan", namespace: "Greenway", author: "Nick Goodey") {
		capability "Initialize"
		capability "Switch"
		capability "Switch Level"
		capability "Polling"

		command "on"
		command "off"

		attribute "Status", "string"

	}

	preferences {

	  section("Device") {
		input("DeviceIndex", "number", title: "Index for device relay or fan", required: true, defaultValue: 0)
	   }
	
		section("URIs") {
			input "URI", "text", title: "Device URI", required: false, defaultValue: "http://192.168.1.17"
   
		}
		
		input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)

	}

}


def installed() {
	log.info "installed..."
}



def updated() {
	if (logEnable) log.info "Updated..."
	initialize()
}

def uninstalled() {
	if (logEnable) log.info "Disconnecting from mqtt"
}


def initialize() {

	if (logEnable) runIn(900, logsOff)

}


def logsOff() {
	log.warn "Debug logging disabled."
	device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def poll() {
   
	 if (logEnable) log.debug "polling..."
	
	 try {
		 
		url = "$settings.URI/?STATUS=1"
		//url = "$settings.URI/status"
		 
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->

			if (resp.success) {
				 
			}
			
			//resp = "${resp.data}"
			
			if (logEnable)
				if (resp.data) log.debug "resp=${resp.data}"
			
			//def respValues = new JsonSlurper().parseText(resp.data.toString().trim())
			
			//sendEvent(name: "Status", value: resp, isStateChange: true)
		}
	} catch (Exception e) {
		log.warn "Call to on failed: ${e.message}"
	}

}

			
			
def on() {
  
	try {
		url = "$settings.URI/?RELAY=$settings.DeviceIndex%201"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			 if (resp.success) {
	 
				sendEvent(name: "switch", value: "on", isStateChange: true)

			}
			if (logEnable)
				if (resp.data) log.debug "${resp.data}"
		}
	} catch (Exception e) {
		log.warn "Call to on failed: ${e.message}"
	}
}

def off() {
	

	try {
		url = "$settings.URI/?RELAY=$settings.DeviceIndex%200"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			if (resp.success) {
			   
				sendEvent(name: "switch", value: "off", isStateChange: true)

			}
			if (logEnable)
				if (resp.data) log.debug "${resp.data}"
		}
	} catch (Exception e) {
		log.warn "Call to off failed: ${e.message}"
	}
}       
			

def setLevel(value, rate = null) {

	if(value < 10) value = 10;
	if(value > 100) value = 100;
	
	//e.g. FAN 1 100 255 max bits
	bits = (int)((value / 100) * 255);


	try {
		url = "$settings.URI/?FAN=$settings.DeviceIndex%20$bits"
		if (logEnable) log.debug url
		
		httpGet(url) { resp ->
			if (resp.success) {
								
				sendEvent(name: "level", value: value)
			}
			if (logEnable){
				log.debug "$bits"
				if (resp.data) log.debug "${resp.data}"
			}
		}
	} catch (Exception e) {
		log.warn "Call to off failed: ${e.message}"
	}

	if (value == 0) {
		sendEvent(name: "switch", value: "off")
	}


	


}

private def displayDebugLog(message) {
	if (logEnable) log.debug "${device.displayName}: ${message}"
}

private def displayInfoLog(message) {

	log.info "${device.displayName}: ${message}"
}

Arduino Remote IO

#include <Arduino_JSON.h>

const int Relay[6] = {2, 4, 7, 8, 12, 13};
const int Fan[6] = {3, 5, 6, 9, 10, 11};

String inputString = "";        
bool stringComplete = false;
String LastCommand = "";
String Status[6] = {"","","","","",""};

void setup() 
{
  // initialize serial:
  Serial.begin(115200);

  pinMode(Relay[0], OUTPUT);
  pinMode(Relay[1], OUTPUT);
  pinMode(Relay[2], OUTPUT);
  pinMode(Relay[3], OUTPUT);
  pinMode(Relay[4], OUTPUT);
  pinMode(Relay[5], OUTPUT);

  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {
  
  if (stringComplete) {
	
	
	
	if(inputString.substring(0,6) == "STATUS")
	{      
	  JSONVar myArray;

	  myArray[0] = analogRead(0);
	  myArray[1] = analogRead(1);
	  myArray[2] = analogRead(2);
	  myArray[3] = analogRead(3);
	  myArray[4] = analogRead(4);
	  myArray[5] = analogRead(5);      
	  myArray[6] = LastCommand;
	  myArray[7] = Status;

	  inputString = JSON.stringify(myArray);

	}
	else if(inputString.substring(0,6) == "RELAY ")
	{
	  int key = getValue(inputString, ' ', 1, 0, 5);
	  int value = getValue(inputString, ' ', 2, 0, 1);

	  digitalWrite(Relay[key], value);

	  Status[key] = inputString.substring(0,6) + " " + key + " " + value;
	}
	else if(inputString.substring(0,7) == "RELAYS ")
	{
	  digitalWrite(Relay[0], getValue(inputString, ' ', 1, 0, 1) );
	  digitalWrite(Relay[1], getValue(inputString, ' ', 2, 0, 1) );
	  digitalWrite(Relay[2], getValue(inputString, ' ', 3, 0, 1) );
	  digitalWrite(Relay[3], getValue(inputString, ' ', 4, 0, 1) );
	  digitalWrite(Relay[4], getValue(inputString, ' ', 5, 0, 1) );
	  digitalWrite(Relay[5], getValue(inputString, ' ', 6, 0, 1) );
	  
	  Status[0] = getValue(inputString, ' ', 1, 0, 1);
	  Status[1] = getValue(inputString, ' ', 2, 0, 1);
	  Status[2] = getValue(inputString, ' ', 3, 0, 1);
	  Status[3] = getValue(inputString, ' ', 4, 0, 1);
	  Status[4] = getValue(inputString, ' ', 5, 0, 1);
	  Status[5] = getValue(inputString, ' ', 6, 0, 1);

	}
	else if(inputString.substring(0,4) == "FAN ")
	{
	  int key = getValue(inputString, ' ', 1, 0, 5);
	  int value = getValue(inputString, ' ', 2, 0, 255);

	  analogWrite(Fan[key], value);

	  Status[key] = inputString.substring(0,6) + " " + key + " " + value;

	}
	else  if(inputString.substring(0,5) == "FANS ")
	{
	  analogWrite(Fan[0], getValue(inputString, ' ', 1, 0, 255) );
	  analogWrite(Fan[1], getValue(inputString, ' ', 2, 0, 255) );
	  analogWrite(Fan[2], getValue(inputString, ' ', 3, 0, 255) );
	  analogWrite(Fan[3], getValue(inputString, ' ', 4, 0, 255) );
	  analogWrite(Fan[4], getValue(inputString, ' ', 5, 0, 255) );
	  analogWrite(Fan[5], getValue(inputString, ' ', 6, 0, 255) );
	  
	  Status[0] = getValue(inputString, ' ', 1, 0, 255);
	  Status[1] = getValue(inputString, ' ', 2, 0, 255);
	  Status[2] = getValue(inputString, ' ', 3, 0, 255);
	  Status[3] = getValue(inputString, ' ', 4, 0, 255);
	  Status[4] = getValue(inputString, ' ', 5, 0, 255);
	  Status[5] = getValue(inputString, ' ', 6, 0, 255);
	}
	
	LastCommand = inputString;
	
	Serial.println(inputString);
	
	inputString = "";
	stringComplete = false;
	
  }//End of if (stringComplete)

}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
	// get the new byte:
	char inChar = (char)Serial.read();
	// add it to the inputString:
	inputString += inChar;
	// if the incoming character is a newline, set a flag so the main loop can
	// do something about it:
	if( (inChar == '\r') || (inChar == '\n') ) {
	  stringComplete = true;
	}
  }
}


int getValue(String data, char separator, int index, int imin, int imax)
{
	int found = 0;
	int strIndex[] = { 0, -1 };
	int maxIndex = data.length() - 1;

	for (int i = 0; i <= maxIndex && found <= index; i++) {
		if (data.charAt(i) == separator || i == maxIndex) {
			found++;
			strIndex[0] = strIndex[1] + 1;
			strIndex[1] = (i == maxIndex) ? i+1 : i;
		}
	}
	String sresp =  found > index ? data.substring(strIndex[0], strIndex[1]) : "";

	return constrain(sresp.toInt(), imin, imax);
}

Wemos D1 R2 WiFi Adapter

#include <Arduino_JSON.h>
#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <ESP8266WebServer.h>

#define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED

String inputString = "";         // a String to hold incoming data
bool stringComplete = false;  // whether the string is complete

WiFiClient net;
MQTTClient MQTTclient; 
ESP8266WebServer server(80);

//WiFi
const char ssid[] = "??????????";
const char pass[] = "?????????";
IPAddress local_IP(192, 168, 1, 17);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0); 

// the IP address for the MQTT server
char MQTTip[] = "192.168.1.71";   

unsigned long UpdateCount = 6;
unsigned long TickCount = UpdateCount-1;
unsigned long PacketCount = 0;
unsigned long lastMillis = 0;

void handleRoot() {

  //http://192.168.1.17/?fan=1&speed=120
  //http://192.168.1.17/?relay=1
  /*
  String message = "RESPONSE\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  */
  
  String message = "";
  
  for (uint8_t i=0; i<server.args(); i++){
	message += " " + server.argName(i) + "=" + server.arg(i) + "\n";
  }

  String payload = server.argName(0) + " " + server.arg(0);

  Serial.println(payload);


  inputString = "";
  
  int tout = 1000;
  
  while( (!Serial.available()) && ((--tout) > 0) )
  {  
	delay(1);
	
	if(tout <= 0)
	{
	  message = "IO TIMEOUT";
	  break;
	}
	else//GET RESPONSE
	{
	  while (Serial.available()) 
	  {    
		char inChar = (char)Serial.read();
		
		if( (inChar == '\r') || (inChar == '\n') )
		{ 
		  message += inputString;      
		}
		else
		{
		  inputString += inChar;
		}
		
	  }//End of while (Serial.available()) 

	  break;
	
	}//End of if(tout <= 0)
	
  }//End of while( (!Serial.available()) && ((--tout) > 0) )
  
  //message += " TOUT=" + String(tout);  
	 
  server.send(200, "text/plain", message);
  
}


void handleNotFound(){
  
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
	message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
 
}

void setup() {

  pinMode(LED_PIN, OUTPUT);
	
  Serial.begin(115200);

 // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet)) {
	Serial.println("STA Failed to configure");
  }
  
  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  MQTTclient.begin(MQTTip, net);
  MQTTclient.onMessage(messageReceived);

  WiFiconnect();
  
  MQTTclient.publish("/ervcontrol/status", "start");    

  MQTTclient.publish("/ervcontrol/status", "run");
  
  server.on("/", handleRoot);

  server.on("/status", [](){
	
	server.send(200, "text/plain", "machine status");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("/ervcontrol/status HTTP server started");
  MQTTclient.publish("/ervcontrol/status", "HTTP server started");
}

void loop() {
  
  MQTTclient.loop();
  
  delay(10);  // <- fixes some issues with WiFi stability

  if (!MQTTclient.connected()) {
	WiFiconnect();
  }

  server.handleClient();
	
  // publish a message roughly every second.
  if (millis() - lastMillis > (UpdateCount * 1000)) 
  { 
	 lastMillis = millis();
	 SendMQTT();
  }
  
  delay(100);
}


void WiFiconnect() {
  
  Serial.print("checking wifi...");
  Serial.println(ssid);
  
  while (WiFi.status() != WL_CONNECTED) {
	Serial.print(".");
	delay(500);
	digitalWrite(LED_PIN, LOW);
	delay(500);
	digitalWrite(LED_PIN, HIGH);
  }
  
  Serial.println("\nWiFi Connected! ");
  Serial.println( WiFi.localIP());

  Serial.print("\nMQTT connecting...");
  while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) {
	Serial.print(".");
	delay(800);
	digitalWrite(LED_PIN, LOW);
	delay(200);
	digitalWrite(LED_PIN, HIGH);
  }

  Serial.println("\nMQTT Connected!");

  digitalWrite(LED_PIN, LOW);
  
  MQTTclient.subscribe("/ervcontrol/control");  
  MQTTclient.publish("/ervcontrol/status", "start");
  MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());

}

void SendMQTT()
{
  digitalWrite(LED_PIN, LOW);
	
  JSONVar myArray;

  
  
  //STALE
  myArray[0] = analogRead(0);
  myArray[1] = analogRead(1);
  myArray[2] = analogRead(2);
  myArray[3] = analogRead(3);
  myArray[4] = analogRead(4);
  myArray[5] = analogRead(5);

  
  //MISC
  myArray[6] = (PacketCount++);
  myArray[7] = WiFi.RSSI();
  
   
  String jsonString = JSON.stringify(myArray);
  
  MQTTclient.publish("/ervcontrol/sensors", jsonString);
  
  digitalWrite(LED_PIN, HIGH);
}

//Test MQTT mon
//Topic: /ervcontrol/control
//Payload: RELAYS 1 0 1 0 1 1 

void messageReceived(String &topic, String &payload) {

  digitalWrite(LED_PIN, LOW);
  
  if(topic == "/ervcontrol/control")
  {  
	if(payload.startsWith("read"))
	{
	   SendMQTT();
	   MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());
	}
	else
	{
	  Serial.println(payload);
	}
  
	MQTTclient.publish("/ervcontrol/payload", topic + "/" + payload);

  }

  digitalWrite(LED_PIN, HIGH);
 
}
2 Likes