We started assembling and flashing the board following Intel instructions at https://software.intel.com/en-us/get-started-edison-windows
As the idea is to get feeds from the rotary grower and be able to change settings such as drum rotation and grow light time on/off periods we have to run MQTT broker and client.
At this stage, the broker will be only accessible on the local network but later I will set up a DNS server so we can access it from everywhere.
Building and Running Mosquitto* MQTT on the Intel Edison board
There are some tips on how to install it on the link below; however, the current version is mosquitto-1.4.12.tar.gz
To have the sketch running from boot and initialize the mosquitto broker I used the script below
#File: /etc/init.d# vi automateSketch.sh exec /sketch/./sketch.elf foo bar & exec mosquitto -d
Make the script executable by changing the permissions with chmod
root@edison:/etc/init.d# chmod +x /etc/init.d/automateSketch.sh
root@edison:/etc/init.d# chmod +x automateSketch.sh
To have the script executed every time Linux boots
root@edison:/etc/init.d# update-rc.d automateSketch.sh defaults
Below are the library files I used to have the MQTT running. I got those files from https://software.intel.com/en-us/blogs/2015/04/06/using-edison-securely-connect-iot-sensor-to-the-internet-with-mqtt
// File: MQTTClient.cpp
#include "MQTTClient.h"
#include <fcntl.h>
/*======================================================================
Constructor/Destructor
========================================================================*/
MQTTClient::MQTTClient()
{
}
MQTTClient::~MQTTClient()
{
close();
}
void MQTTClient::close()
{
if (spipe) {
fclose(spipe);
}
}
/*========================================================================
Initialization. Store variables to be used for subscribe/publish calls
==========================================================================*/
void MQTTClient::begin(char *broker, int port, security_mode smode,
char* cafile, char *user, char *psk)
{
strcpy(mqtt_broker, broker);
serverPort = port;
mode = smode;
if (mode == SSL) {
strcpy(certificate_file, cafile);
}
else if (mode == PSK) {
strcpy(psk_identity, user);
strcpy(psk_password, psk);
}
Serial.println("MQTTClient initialized");
Serial.print("Broker: "); Serial.println(mqtt_broker);
Serial.print("Port: "); Serial.println(serverPort);
Serial.print("Mode: "); Serial.println(mode);
}
/*=======================================================================
Subscribe to a topic, (*callback) is a function to be called when client
receive a message
=========================================================================*/
boolean MQTTClient::subscribe(char* topic,
void (*callback)(char* topic, char* message))
{
char cmdString[256];
if (mqtt_broker == NULL) {
return false;
}
if (topic == NULL) {
return false;
}
callback_function = callback;
switch(mode) {
case OPEN:
sprintf(cmdString,
"mosquitto_sub -h %s -p %d -t %s -v",
mqtt_broker, serverPort, topic);
break;
case SSL:
sprintf(cmdString,
"mosquitto_sub -h %s -p %d -t %s -v --cafile %s",
mqtt_broker, serverPort, topic, certificate_file);
break;
case PSK:
sprintf(cmdString,
"mosquitto_sub -h %s -p %d -t %s -v --psk-identity %s --psk %s",
mqtt_broker, serverPort, topic, psk_identity, psk_password);
break;
default:
break;
}
if ((spipe = (FILE*)popen(cmdString, "r")) != NULL) {
// we need to set the pipe read mode to non-blocking
int fd = fileno(spipe);
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
strcpy(topicString, topic);
return true;
}
else {
return false;
}
}
/*====================================================================
Check if there is data in the pipe,
if true, parse topic and message and execute callback function
return if pipe is empty
======================================================================*/
boolean MQTTClient::loop()
{
if (fgets(dataBuffer, sizeof(dataBuffer), spipe)) {
parseDataBuffer();
callback_function(topic, message);
}
}
/*====================================================================
Publish a message on the given topic
======================================================================*/
boolean MQTTClient::publish(char *topic, char *message)
{
FILE* ppipe;
char cmdString[256];
boolean retval = false;
if (this->mqtt_broker == NULL) {
return false;
}
if (topic == NULL) {
return false;
}
switch (this->mode) {
case OPEN:
sprintf(cmdString,
"mosquitto_pub -h %s -p %d -t %s -m \"%s\" %s",
mqtt_broker, serverPort, topic, message, retain_flag?"-r":"");
break;
case SSL:
sprintf(cmdString,
"mosquitto_pub -h %s -p %d --cafile %s -t %s -m \"%s\" %s",
mqtt_broker, serverPort, certificate_file,
topic, message, retain_flag?"-r":"");
break;
case PSK:
sprintf(cmdString,
"mosquitto_pub -h %s -p %d --psk-identity %s --psk %s -t %s -m \"%s\" %s",
mqtt_broker, serverPort, psk_identity, psk_password,
topic, message, retain_flag?"-r":"");
break;
}
if (!(ppipe = (FILE *)popen(cmdString, "w"))) {
retval = false;
}
if (fputs(cmdString, ppipe) != EOF) {
retval = true;
}
else {
retval = false;
}
fclose(ppipe);
return retval;
}
/*======================================================================
Parse data in the data buffer to topic and message buffer
delimiter is the first space
if there is only one recognizable string, it is assumed a message
string and topic is set to NULL
========================================================================*/
void MQTTClient::parseDataBuffer()
{
topic = dataBuffer;
message = dataBuffer;
while((*message) != 0) {
if ((*message) == 0x20) {
// replace the first space with the NULL character
(*message) = 0;
message++;
break;
}
else {
message++;
}
}
if (strlen(message) == 0) {
topic = NULL;
message = dataBuffer;
}
}
// File: MQTTClient.h /* Minimalist MQTT Client using mosquitto_sub and mosquitto_pub Assume Mosquitto MQTT server already installed and mosquitto_pub/sub are in search path */ #ifndef __MQTTClient_H__ #define __MQTTClient_H__ #include <Arduino.h> #include <stdio.h> enum security_mode {OPEN = 0, SSL = 1, PSK = 2}; class MQTTClient { public: MQTTClient(); ~MQTTClient(); void begin(char * broker, int port, security_mode mode, char* certificate_file, char *psk_identity, char *psk); boolean publish(char *topic, char *message); boolean subscribe(char* topic, void (*callback)(char *topic, char* message)); boolean loop(); boolean available(); void close(); private: void parseDataBuffer(); FILE* spipe; char mqtt_broker[32]; security_mode mode; char topicString[64]; char certificate_file[64]; char psk_identity[32]; char psk_password[32]; int serverPort; char *topic; char *message; boolean retain_flag; void (*callback_function)(char* topic, char* message); char dataBuffer[256]; }; #endif
Main Sketch
This sketch is basically running only the basic functions but I will keep updating as I implement more sensors and features.
//Rotary Growing System // MQTT #include <stdio.h> #include <Arduino.h> #include "MQTTClient.h" #define SECURE_MODE 0 // 0 = No security // 1 = SSL security // 2 = TLS-PSK security MQTTClient mqttClient; unsigned long mqttPubInterval_1 = 1000; // interval for publishing critical values unsigned long mqttPubInterval_2 = 5000; // interval for publishing non-critical values unsigned long previousMqttMillis = 0; char fmtString[256]; // utility string char topicString[64]; // topic for publishing char msgString[14]; // message bool autoManual = 1; // mode initially set to automatic unsigned long currentMillis; //Stepper Motor int drumRPH = 5; // default drum rotation set at 5 Rotation per Hour const int stepperPin = 3; // stepper pulse pin const int stepperENA = 4; // stepper enable pin int stepperInterval; // interval at which to pulse (milliseconds) calculated dividing 2400 / required drum RPH unsigned long previousStepperMillis = 0; // for stepper motor pulse bool remoteDrum = 0; // value received from MQTT pub to set drum ON or OFF remotely, default OFF "0" //Grow Light const int growLight = 5; // growLight pin int set_on = 8; // default on time int set_off = 20; // default off time char date[100]; // date display format bool remoteGrowLight = HIGH; // value received from MQTT pub to set light ON or OFF remotely, default OFF "HIGH" //Water Pump const int waterPump = 6; // water pump relay pin unsigned long pumpInterval = 1800000; // interval pump will switch on (every 30 min) unsigned long pumpOnTime = 10000; // default time pump stays on every cycle (10 seconds) unsigned long previousPumpMillis = 0; // for water pump timer bool remoteWaterPump = HIGH; // value received from MQTT pub to set water pump ON or OFF remotely, default OFF "HIGH" //Temperature Sensor const int pinTempSen = A0; // pin of temperature sensor float temp; //Light Sensor const int pinLightSen = A3; // pin of light sensor int lightSensor = 0; int lux; //LCD DISPLAY #include <rgb_lcd.h> #include <Wire.h> rgb_lcd lcd; int screen = 0; int screenMax = 5; bool screenChanged = true; // initially we have a new screen, by definition unsigned long previousLCDMillis = 0; // for LCD screen update unsigned long lcdInterval = 4000; // LCD change interval char timenow[50]; // defines of the screens to show #define TEMPERATURE 0 #define LUX 1 #define RPH 2 #define TIME 3 #define HUMIDITY 4 #define PH 5 void setup() { pinMode(growLight, OUTPUT); // light pin digitalWrite (growLight, HIGH); // relay switches off at HIGH pinMode(waterPump, OUTPUT); // waterPump pin digitalWrite (waterPump, HIGH); // relay switches off at HIGH pinMode(stepperENA, OUTPUT); // stepper Enable pin digitalWrite (stepperENA, LOW); // stepper Enable OFF pinMode(stepperPin, OUTPUT); // stepper motor pulse pin Serial.begin(9600); //initialize serial communications at 9600 bps Serial.println ("\n Initialising Rotary Growing System"); lcd.begin(16, 2); // set up the LCD's number of columns and rows lcd.clear(); lcd.setCursor(0, 0); // set the cursor to column 0, line 0 lcd.print("Initialising..."); delay(2000); lcd.clear(); lcd.setCursor(1, 0); // set the cursor to column 1, line 0 lcd.print("Rotary Growing"); lcd.setCursor(5, 1); // set the cursor to column 5, line 1 lcd.print("System"); delay(3000); // initializing MQTTClient #if ( SECURE_MODE == 0 ) Serial.println("No security"); mqttClient.begin("localhost", 1883, OPEN, NULL, NULL, NULL); #elif ( SECURE_MODE == 1 ) Serial.println("SSL security"); mqttClient.begin("localhost", 1994, SSL, "/home/mosquitto/certs/ca.crt", NULL, NULL); #elif ( SECURE_MODE == 2 ) Serial.println("TLS-PSK security"); mqttClient.begin("localhost", 1995, PSK, NULL, "user", "deadbeef"); #endif // subscribe to all topics published under edison mqttClient.subscribe("edison/#", mqttCallback); mqttClient.publish("edison/sysMsg", "Rotary Growing System Booted"); mqttClient.publish("edison/autoManual", "A"); // publish default Mode to sincronise remote values sprintf(msgString, "%d", drumRPH); // read default drumRPH value mqttClient.publish("edison/drumRPH", msgString); // publish default drumRPH to sincronise remote values sprintf(msgString, "%d", set_on); // read default set_on value mqttClient.publish("edison/set_on", msgString); // publish default light set_on time to sincronise remote values sprintf(msgString, "%d", set_off); // read default set_off value mqttClient.publish("edison/set_on", msgString); // publish default light set_off time to sincronise remote values } // MQTT Callback function void mqttCallback(char* topic, char* message) { sprintf(fmtString, "mqttCallback(), topic: %s, message: %s", topic, message); Serial.print(fmtString); // Setting Drum RPH if (strcmp(topic, "edison/drumRPH") == 0) { //check the topic then execute command as appropriate drumRPH = atoi(message); } //Seting grow light time ON if (strcmp(topic, "edison/set_on") == 0) { //check the topic then execute command as appropriate set_on = atoi(message); } //Seting grow light time OFF if (strcmp(topic, "edison/set_off") == 0) { //check the topic then execute command as appropriate set_off = atoi(message); } //Set Auto or Manual Mode if (strcmp(topic, "edison/autoManual") == 0) { //check the topic then execute command as appropriate if (message[0] == 'A') { //set mode to 1 if Automatic autoManual = 1; } else { autoManual = 0; //set mode to 0 if Manual } } // Switching Drum ON / OFF if (strcmp(topic, "edison/remoteDrum") == 0) { // then execute command as appropriate if (strncmp(message, "ON", 2) == 0) { remoteDrum = 1; } else { remoteDrum = 0; } } //Grow Light remote control if (strcmp(topic, "edison/remoteGrowLight") == 0) { // then execute command as appropriate if (strncmp(message, "ON", 2) == 0) { remoteGrowLight = LOW; } else { remoteGrowLight = HIGH; } } //Water Pump remote control if (strcmp(topic, "edison/remoteWaterPump") == 0) { // then execute command as appropriate if (strncmp(message, "ON", 2) == 0) { remoteWaterPump = LOW; } else { remoteWaterPump = HIGH; } } } void loop() { mqttClient.loop(); // check for any new message from mqtt_sub currentMillis = millis(); stepperMotor(); lightTimer(); waterPumpTimer(); grooveTempSen(); grooveLightSen(); grooveLCD(); edisonMQTT(); } //======================================================================================================================================== void stepperMotor() { // check to see if it's time to pulse; that is, if the // difference between the current time and last pulse time // is bigger than the interval at which you want to pulse stepperInterval = 2400/drumRPH; bool pulseState; // pulseState used to set the stepperPin if (autoManual == 1 || (autoManual == 0 && remoteDrum == 1)){ digitalWrite(stepperENA, LOW); if (currentMillis - previousStepperMillis >= stepperInterval) { previousStepperMillis = currentMillis; // save the last Pulse time // if the Pulse is off turn it on and vice-versa: if (pulseState == LOW) { pulseState = HIGH; } else { pulseState = LOW; } // set the stepperPin with the pulseState of the variable: digitalWrite(stepperPin, pulseState); } } else { pulseState = LOW; digitalWrite(stepperENA, HIGH); } } //======================================================================================================================================== void lightTimer() { time_t t = time(NULL); struct tm tm = *localtime(&t); sprintf(date, "%02d/%02d/%04d %02d:%02d:%02d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); sprintf(timenow, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); Serial.println(timenow); //if(set_on == set_off){ // digitalWrite(growLight, HIGH); //HIGH sets light relay off // Serial.println("Same On and OFF hours. Set at least 1h difference"); //} // //if(set_on >23){ // digitalWrite(growLight, HIGH); //HIGH sets light relay off // Serial.println("Invalid hour value, choose from 0 until 23"); //} // //if(set_off >23){ // digitalWrite(growLight, HIGH); //HIGH sets light relay off // Serial.println("Invalid hour value, choose from 0 until 23"); //} if (autoManual == 1) { if (tm.tm_hour >= set_on && tm.tm_hour < set_off) //Start timer { digitalWrite(growLight, LOW); //LOW sets light relay on } else { digitalWrite(growLight, HIGH); //HIGH sets light relay off } } else { Serial.println("Manual Mode Selected"); digitalWrite(growLight, remoteGrowLight); } } //======================================================================================================================================== void waterPumpTimer() { // check to see if it's time to switch the water pump; if the // difference between the current time and last time the pump was on // is bigger than the interval set on pumpInterval // than switch it on for the time set on pumpOnTime if (autoManual == 1) { if (currentMillis - previousPumpMillis >= pumpInterval) { previousPumpMillis = currentMillis; // save the last pump interval on time unsigned long previousPumpOnMillis; if (currentMillis - previousPumpOnMillis >= pumpOnTime) { previousPumpOnMillis = currentMillis; // save the last pump time on time digitalWrite(waterPump, LOW); // turn water pump ON } else { digitalWrite(waterPump, HIGH); // turn water pump OFF } } else { digitalWrite(waterPump, HIGH); // turn water pump OFF } } else { digitalWrite(waterPump, remoteWaterPump); } } //======================================================================================================================================== void grooveTempSen() // grove temperature sensor { int B = 3975; // B value of the thermistor float resistance; int val = analogRead(pinTempSen); // get analog value resistance = (float)(1023 - val) * 10000 / val; // get resistance temp = 1 / (log(resistance / 10000) / B + 1 / 298.15) - 273.15; // calc temperature // Serial.print("Temp: "); // Serial.print(temp); // Serial.println(" ºC"); } //======================================================================================================================================== void grooveLightSen() // grove light sensor { lightSensor = analogRead(pinLightSen); lux = lightSensor; // Serial.print("Light: "); // Serial.print(lux); // Serial.println(" LUX"); } //======================================================================================================================================== void grooveLCD() { // groove LCD RGB display unsigned long currentLCDMillis = millis(); char number[4]; { if (currentLCDMillis - previousLCDMillis > lcdInterval) // save the last time you changed the display { previousLCDMillis = currentLCDMillis; screen++; if (screen > screenMax) screen = 0; // all screens done? => start over screenChanged = true; } // debug Serial.println(screen); // DISPLAY CURRENT SCREEN if (screenChanged) // only update the screen if the screen is changed. { screenChanged = false; // reset for next iteration switch (screen) { case TEMPERATURE: //print temperature lcd.clear(); lcd.print("Temperature"); lcd.setCursor(0, 1); sprintf(number, "%02.01f", temp); lcd.setCursor(3, 1); lcd.print(number); lcd.print(" C"); break; case LUX: //print light lcd.clear(); lcd.print("Light"); lcd.setCursor(0, 1); sprintf(number, "%d", lux); lcd.setCursor(3, 1); lcd.print(number); lcd.print(" LUX"); break; case RPH: //print RPM // drum rotation per hour lcd.clear(); lcd.print("Drum Speed"); lcd.setCursor(0, 1); sprintf(number, "%d", (drumRPH)); lcd.setCursor(3, 1); lcd.print(number); lcd.print(" RPH"); break; case TIME: //print TIME lcd.clear(); lcd.print(timenow); lcd.setCursor(0, 1); lcd.print("GROW LIGHT: "); lcd.setCursor(13, 1); if (digitalRead (growLight) == LOW) { lcd.print("ON"); } else { lcd.print("OFF"); } break; case HUMIDITY: //print humidity lcd.clear(); lcd.print("Humidity"); lcd.setCursor(0, 1); //sprintf(number,"%d",humid); lcd.setCursor(3, 1); lcd.print(38); lcd.print(" %"); break; case PH: //print PH lcd.clear(); lcd.print("PH"); lcd.setCursor(0, 1); //sprintf(number,"%02.01f",ph); lcd.setCursor(3, 1); lcd.print(6.5); default: // cannot happen -> showError() ? break; } } } } //======================================================================================================================================== void edisonMQTT() { //critical information publishes immediately if (currentMillis - previousMqttMillis >= mqttPubInterval_1) { previousMqttMillis = currentMillis; // save the last Pulse time // publish drum Stopped if (stepperENA == HIGH){ mqttClient.publish("edison/sysMsg", "DRUM STOPPED"); } // publish light issue if ((digitalRead (growLight) == LOW) && (lux < 500)) { mqttClient.publish("edison/sysMsg", "FAULTY LIGHT"); } // publish Auto/Manual mode change bool modeState; // indicates toggle on mode state if (modeState == LOW && autoManual == 1) { mqttClient.publish("edison/sysMsg", "MODE SET TO: AUTO"); modeState = HIGH; } if (modeState == HIGH && autoManual == 0) { mqttClient.publish("edison/sysMsg", "MODE SET TO: MANUAL"); modeState = LOW; } // publish grow light switching on or off bool lightState; // indicates toggle on light state if (lightState == LOW && (digitalRead (growLight) == HIGH)) { mqttClient.publish("edison/growLight", "OFF"); lightState = HIGH; } if (lightState == HIGH && (digitalRead (growLight) == LOW)) { mqttClient.publish("edison/growLight", "ON"); lightState = HIGH; } // publish grow light switching on or off bool waterPumpState; // indicates toggle on water pump state if (waterPumpState == LOW && (digitalRead (waterPump) == HIGH)) { mqttClient.publish("edison/waterPump", "OFF"); waterPumpState = HIGH; } if (waterPumpState == HIGH && (digitalRead (waterPump) == LOW)) { mqttClient.publish("edison/waterPump", "ON"); waterPumpState = HIGH; } } //non-critical information publishes every 10 minutes if (currentMillis - previousMqttMillis >= mqttPubInterval_2) { previousMqttMillis = currentMillis; // save the last Pulse time // publish temperature sprintf(msgString, "%02.01f", temp); mqttClient.publish("edison/temperature", msgString); // publish light sensor reading sprintf(msgString, "%d", lux); mqttClient.publish("edison/lightSensor", msgString); // // publish hunidity sensor reading // sprintf(msgString, "%d", humid); // mqttClient.publish("edison/humidity", msgString); // // // publish PH // sprintf(msgString, "%02.01f", ph); // mqttClient.publish("edison/ph", msgString); } } //END
On my mobile, I installed MQTT Dashboard so we can get the feeds and publish values such as Light Time Set On/Off and Drum RPH (Rotations per hour)