Hubduino Question

Okay, so you want to simply update a custom attribute, correct?

Does the ESP8266 need to receive any data fro Hubitat? Or is the traffic one way only?

At them moment it will be one way only ESP8266 ---> Hubitat.

@JohnRob - Here is some simple example code that should get you started. This does NOT use the normal HubDuino Drivers for Hubitat, as those are designed for using the ST_Anything sketches/libraries. This sketch + driver matched pair is much simpler and should give you a pretty solid foundation to start building a custom sketch that meets your unique needs. The sketch does use the 'SmartThings..." communication Arduino libraries.

ESP8266 Sketch (hint: use the 'sendData(String)' function to send data to the Hubitat custom attribute called 'data'. You can tweak the name 'data' as you see fit, but the names much match in the sketch and driver.)

//*****************************************************************************
/// @file
/// @brief
///   Arduino SmartThings Ethernet ESP8266 WiFi On/Off with LED Example 
///
///   Revised by Dan Ogorchock on 2017-02-11 to work with new "SmartThings v2.0" Library
///
///   Notes: The NodeMCU ESP communicates via WiFi to your home network router,
///          then to the ST/Hubitat Hub
///
///
//*****************************************************************************

#include <SmartThingsESP8266WiFi.h>

//*****************************************************************************
// Pin Definitions    | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                    V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
//******************************************************************************************
//NodeMCU ESP8266 Pin Definitions (makes it much easier as these match the board markings)
//******************************************************************************************
//#define LED_BUILTIN 16
//#define BUILTIN_LED 16
//
//#define D0 16
//#define D1  5
//#define D2  4
//#define D3  0
//#define D4  2
//#define D5 14
//#define D6 12
//#define D7 13
//#define D8 15
//#define D9  3
//#define D10 1


#define PIN_LED LED_BUILTIN  //Onboard LED on most NodeMCU ESP8266 boards
//#define PIN_LED D4  //Onboard LED for NodeMCU v3 board (the wide one)

//*****************************************************************************
// Global Variables   | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                    V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
SmartThingsCallout_t messageCallout;    // call out function forward decalaration

//******************************************************************************************
//ESP8266 WiFi Information    CHANGE THIS INFORMATION ACCORDINGLY FOR YOUR NETWORK!
//******************************************************************************************
String str_ssid     = "yourSSIDhere";                            //  <---You must edit this line!
String str_password = "yourWiFiPasswordhere";                          //  <---You must edit this line!
IPAddress ip(192, 168, 1, 221);       // Device IP Address      //  <---You must edit this line!
IPAddress gateway(192, 168, 1, 1);    //router gateway          //  <---You must edit this line!
IPAddress subnet(255, 255, 255, 0);   //LAN subnet mask         //  <---You must edit this line!
IPAddress dnsserver(192, 168, 1, 1);  //DNS server              //  <---You must edit this line!
const unsigned int serverPort = 8090; // port to run the http server on

// Smartthings/Hubitat Hub Information
IPAddress hubIp(192, 168, 1, 143);    // smartthings/hubitat hub ip     //  <---You must edit this line!
//const unsigned int hubPort = 39500;   // smartthings hub port
const unsigned int hubPort = 39501;   // hubitat hub port


//Create a SmartThings Ethernet ESP8266WiFi object
st::SmartThingsESP8266WiFi smartthing(str_ssid, str_password, ip, gateway, subnet, dnsserver, serverPort, hubIp, hubPort, messageCallout);

bool isDebugEnabled;    // enable or disable debug in this example


//*****************************************************************************
// Local Functions  | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                  V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
void on()
{
  digitalWrite(PIN_LED, LOW);  // turn LED on
  smartthing.send("switch:on");        // send message to cloud
}

void off()
{
  digitalWrite(PIN_LED, HIGH);   // turn LED off
  smartthing.send("switch:off");       // send message to hub
}

//Use the function to send any string to the custom attribute called 'data' in the Hubitat Driver
void sendData(String data)
{
  smartthing.send("data:" + data);     // send message to hub
}

//*****************************************************************************
// API Functions    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                  V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
void setup()
{
  // setup default state of global variables
  isDebugEnabled = true;

  if (isDebugEnabled)
  { // setup debug serial port
    Serial.begin(115200);         // setup serial with a baud rate of 9600
    Serial.println("");
    Serial.println("setup..");  // print out 'setup..' on start
  }
  
  // setup hardware pins 
  pinMode(PIN_LED, OUTPUT);     // define PIN_LED as an output

  //Run the SmartThings init() routine to make sure the MCU is connected to the Hub
  smartthing.init();

  //synch up the data to the hub
  off();                                 // send message to hub & set digital output pin off
  sendData("initialData");               // send message to hub
}

//*****************************************************************************
void loop()
{
  // run communications logic
  smartthing.run();
}

//*****************************************************************************
void messageCallout(String message)
{
  // if debug is enabled print out the received message
  if (isDebugEnabled)
  {
    Serial.print("Received message: '");
    Serial.print(message);
    Serial.println("' ");
  }

  // if message contents equals to 'switch:on' then call on() function
  // else if message contents equals to 'switch:off' then call off() function
  if (message.equals("switch:on"))
  {
    on();
  }
  else if (message.equals("switch:off"))
  {
    off();
  }
  else if (message.equals("refresh"))
  {
    sendData("finalData");
  }
}

Hubitat Driver Code

/**
 *  HubDuino_Ethernet_On_Off.groovy
 *
 *  Copyright 2018 Dan G Ogorchock 
 *
 *  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.
 *
 *  Change History:
 *
 *    Date        Who            What
 *    ----        ---            ----
 *    2018-08-17  Dan Ogorchock  Original Creation
 *	
 */
 
metadata {
	definition (name: "HubDuino Ethernet On Off", namespace: "ogiewon", author: "Dan Ogorchock") {
        capability "Configuration"
        capability "Refresh"
        capability "Switch"
        capability "Signal Strength"  
        
        attribute "data", "string"     
	}

    simulator {
    }

    // Preferences
	preferences {
		input "ip", "text", title: "Arduino IP Address", description: "IP Address in form 192.168.1.226", required: true, displayDuringSetup: true
		input "port", "text", title: "Arduino Port", description: "port in form of 8090", required: true, displayDuringSetup: true
		input "mac", "text", title: "Arduino MAC Addr", description: "MAC Address in form of 02A1B2C3D4E5", required: true, displayDuringSetup: true
	}
}

// parse events into attributes
def parse(String description) {
	//log.debug "Parsing '${description}'"
	def msg = parseLanMessage(description)
	def headerString = msg.header
	def bodyString = msg.body

	if (bodyString) {
        log.debug "Parsing: $bodyString"
        if (bodyString.startsWith("rssi")) {
            bodyString = bodyString.replaceAll(" ", ":")
        }
    	def parts = bodyString.split(":")
    	def name  = parts.length>0?parts[0].trim():null
    	def value = parts.length>1?parts[1].trim():null
		
        def results = []
        results << createEvent(name: name, value: value, isStateChange: true)
		log.debug results
		return results
    }
}

private getHostAddress() {
    def ip = settings.ip
    def port = settings.port

	log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
    return ip + ":" + port
}

def sendData(message) {
    sendEthernet(message) 
}

def sendEthernet(message) {
    if (message.contains(" ")) {
        def parts = message.split(" ")
        def name  = parts.length>0?parts[0].trim():null
        def value = parts.length>0?parts[1].trim():null
        message = name + "%20" + value
    }
	log.debug "Executing 'sendEthernet' ${message}"
	if (settings.ip != null && settings.port != null) {
    	new hubitat.device.HubAction(
    		method: "POST",
    		path: "/${message}?",
    		headers: [ HOST: "${getHostAddress()}" ]
		)
    }
    else {
    	log.debug "Parent HubDuino Ethernet Device: Please verify IP address and Port are configured."    
    }
}

// handle commands
def on() {
	log.debug "Executing 'on()'"
	sendEthernet("switch:on") 
}

def off() {
	log.debug "Executing 'off()'"
	sendEthernet("switch:off")    
}

def configure() {
	log.debug "Executing 'configure()'"
    updateDeviceNetworkID()
}

def refresh() {
	log.debug "Executing 'refresh()'"
	sendEthernet("refresh")
}

def installed() {
	log.debug "Executing 'installed()'"
    if ( device.deviceNetworkId =~ /^[A-Z0-9]{12}$/)
    {
        
    }
    else
    {
        log.error "Parent HubDuino Ethernet Device has not been fully configured."
    }
}

def initialize() {
	log.debug "Executing 'initialize()'"
}

def updated() {
	if (!state.updatedLastRanAt || now() >= state.updatedLastRanAt + 5000) {
		state.updatedLastRanAt = now()
		log.debug "Executing 'updated()'"
    	updateDeviceNetworkID()
        log.debug "Hub IP Address = ${device.hub.getDataValue("localIP")}"
        log.debug "Hub Port = ${device.hub.getDataValue("localSrvPortTCP")}"
	}
	else {
		log.trace "updated(): Ran within last 5 seconds so aborting."
	}
}

def updateDeviceNetworkID() {
	log.debug "Executing 'updateDeviceNetworkID'"
    def formattedMac = mac.toUpperCase()
    formattedMac = formattedMac.replaceAll(":", "")
    if(device.deviceNetworkId!=formattedMac) {
        log.debug "setting deviceNetworkID = ${formattedMac}"
        device.setDeviceNetworkId("${formattedMac}")
	}
}
1 Like

@ogiewon,

Thank you so much. I have it up and running. Now I have to study it to learn how to integrate it into my plan. But this part is good for me because it's at a level I can understand :grinning:

Regards,
John

1 Like

Glad to hear it is working. By removing all of the C++ code of ST_Anything, and condensing things back down to a single sketch, it really should be much more straightforward. If you have any questions, feel free to ask.

One thing to be careful of is to not add delay() statements to your sketch's loop() routine or the callback() routine (rally, the setup() routine is the only place you can use them safely.) The Arduino delay() statement is a blocking call and will prevent the timely execution of the smartthings.run() call which handles the networking behind the scenes.

Good luck with your project and have fun!

@ogiewon

Again thank you :grinning:
I understand delays are bad boys.
I have your code up and running with more a relevant attribute name :slight_smile:

I have another ESP8266 on the way and plan on moving my Pro Mini code to the ESP. Then when I get it working there I will move it into the ino file.

Thanks
John

@ogiewon

Not to be a bother but could you tell me where the RSSI update interval is controlled?

When I look at SmartThingsESP8266WiFI.cpp & .h the RSSI interval appears to be every 5 seconds however my hub active log shows 1 minute.

I could not find how the 1 minute came about.

Could you enlighten me?

Thanks
John

Inside the .cpp file, you find the following section...

When the MCU starts up, it sends every 5 seconds, but then increases the time slowly to once a minute so as not to flood the network/logs. The final time value of 60000ms is defined in SmartThingsEthernet.h

if (millis() - previousMillis > RSSIsendInterval)
			{

				previousMillis = millis();

				if (RSSIsendInterval < RSSI_TX_INTERVAL)
				{
					RSSIsendInterval = RSSIsendInterval + 1000;
				}
				
				strRSSI = String("rssi ") + String(WiFi.RSSI());
				send(strRSSI);

				if (_isDebugEnabled)
				{
					Serial.println(strRSSI);
				}
			}

@ogiewon

Hi, I know this is an old thread but I was hoping you could provide some guidance sending data from NodeMCU to Hubitat using your above code.

I want to send 5 bytes of information from my NodeMCU to Hubitat driver. I was thinking I could use an array of some sort and end up with MAP'ed data on the Hubitat. I am however at a loss to understand how to send and receive such data. Any help would be much appreciated.

John

If you have a simple sketch, and don’t need the structure of ST_Anything, I would look at simply using the “SmartThings...” libraries in my Github repository. This set of Arduino libraries is the communications software used by ST_Anything. There should be an ESP8266 example sketch in the SmartThings library folder that should help. It shows simple communications... however, the driver associated with this example is still specific to SmartThings, IIRC. So, you’d need probably start with the HubDuino Parent driver and strip it down, if you don’t want to use the child devices.

It really depends on what you’re trying to accomplish. If you can provide some details, I can make a better recommendation.

1 Like

I'm currently using the code you posted that uses SmartThings code to create a link to Hubitat. However I can only send one bit of data in the code below.

Part of Current C++ code running on the NodeMCU:

#include <SmartThingsESP8266WiFi.h>
.
.
.
//Use the function to send any string to the
// custom attribute called 'data' in the Hubitat Driver
void sendData(String data)
   {
      smartthing.send("data:" + data);     // send message to hub
   }

I would like to send 5 bytes of data instead of the one, this is where I need a recommendation. I guess I could convert my data to a string, concatonate with comma's between and send as one bit of data.

Then in the groovy parse method decode it. Hence my question: is this the best way to approach such a transfer? Or is there a way to send the C++ array to a groovy map?

Current groovy parse method:

def parse(String description) {
log.debug "Parsing '${description}'"
def msg = parseLanMessage(description)     // 
def headerString = msg.header
def bodyString = msg.body
state.KeypadUpdate = true

if (bodyString) {
        log.debug "Parsing: $bodyString"
       if (bodyString.startsWith("rssi")) {
           bodyString = bodyString.replaceAll(" ", ":")
          }
	
	def parts = bodyString.split(":")
	def name  = parts.length>0?parts[0].trim():null
	def value = parts.length>1?parts[1].trim():null
	
    def results = []
    results << createEvent(name: name, value: value, isStateChange: true)			// seems to always be true??
	log.debug results
	return results
    }  // --- end of   if(bodyString)
}

Thanks
John

All data sent is being sent as a string already. So, I would recommend that you send the data as a JSON encoded string. Then, you should be able to easily parse the result on the groovy side.

Here is an old ST example that Chuck Schwer @chuck.schwer wrote many moons ago... Back then, JSON was a mystery to me, so I opted to keep my very simple, space-delimited name/value pairs that had been serving me well with the old ST ThingShield. In hindsight, I really should have embraced JSON.

You will need to properly build the JSON string before sending it from the Arduino Sketch. There are some open source JSON libraries for the Arduino IDE that should help to simplify this. You can also brute force it if the data being sent is always in the exact same format (e.g. always 5 values, with the same unique names.)

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
	def msg = parseLanMessage(description)
	def headerString = msg.header

	if (!headerString) {
		log.debug "headerstring was null for some reason :("
    }

	def result = []
	def bodyString = msg.body
    def value = "";
	if (bodyString) {
        log.debug "BodyString: $bodyString"
        // default the contact and motion status to closed and inactive by default
        def allContactStatus = "closed"
        def allMotionStatus = "inactive"
        def json = bodyString.json;
        log.debug "JSON: $json"
        json?.house?.door?.each { door ->
            value = door?.status == 1 ? "open" : "closed"
            log.debug "${door.name} door status ${value}"
            // if any door is open, set contact to open
            if (value == "open") {
				allContactStatus = "open"
			}
			result << createEvent(name: "${door.name}door", value: value)
        }
        json?.house?.motion?.each { motion ->
			value = motion?.status == 1 ? "active" : "inactive"
			log.debug "${motion.name} motion status ${value}"
			// if any motion sensor is active, set motion to active
			if (value == "active") {
				allMotionStatus = "active"
			}
			result << createEvent(name: "${motion.name}motion", value: value)
		}

		result << createEvent(name: "contact", value: allContactStatus)
		result << createEvent(name: "motion", value: allMotionStatus)
    }
    result
}

Hope this gets you pointed in the right direction.

1 Like

Many many thanks :smiley:
It looks to be just what I need. I could probably use the brute force method for my current application but will head in the more general direction for the learning and future use.

John

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.