Brian RashapClint Wolf
Published © GPL3+

Laser Engraver Monitoring and Automatic Exhaust

Increasing MakerSpace resident safety and saving energy by automatically turning the exhaust system on or off when laser power is detected.

AdvancedFull instructions provided12 hours60
Laser Engraver Monitoring and Automatic Exhaust

Things used in this project

Hardware components

Argon
Particle Argon
×2
Neopixel WS2812B by Breakout
×2
Feather I2C Shield
×1
Gikfun solderable breadboard
×1
ABS Junction/Project Boxes
×2
Single channel relay module
×1
National Control Devices NCD AC Current Monitor
×2

Software apps and online services

VS Code
Microsoft VS Code
Fritzing
Adafruit Dashboard IO

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Flux, Soldering
Solder Flux, Soldering
Brightech LightView PRO LED 2.25x Magnifying Glass Clamp Lamp
Extech Autoranging Mini Multimeter MN16A
GB Electric Wire Cutter/Stripper

Story

Read more

Custom parts and enclosures

Internals

Schematics

Current Sensor Unit Wiring

Current Monitor Fritzing as PNG

Code

Epilog Current Monitor

C/C++
Monitor current to determine power state
/*
 * Project CurrentMonitor
 *
 * Description: This is program 1 of 2 for the AutoVenitlation project. 
 * Current-monitoring sensors will be placed on each power cable of two Epilog Laser Unit machines.
 * When the state of power changes (from ON to OFF or OFF to ON) on either or both Epilog units, this 
 * program will PUBLISH the state change to the Adafruit dashboard. 
 * This program's sole purpose is to monitor the power and PUBLISH this state change.
 * 
 * There will be a second companion program to this one that will SUBSCRIBE to the same dashboard. When
 * a state change is detected, that second program will send a signal to a relay that will either turn on or turn off
 * an exhaust fan.
 * 
 * Author: Clint Wolf
 * 
 * Date: Project inception in September 2021
 */

#include <JsonParserGeneratorRK.h>  // Searched for and installed
#include <Particle.h>               // Search yielded no results. Did not install, but compiled successfully anyway
#include <Wire.h>                   // Search yielded no results. Did not install, but compiled successfully anyway

#include <neopixel.h>               // Searched for neopixel. Found and installed

// Adafruit.io Set Up BEGIN
#include <Adafruit_MQTT.h>          // Searched for Adafruit_MQTT. Found and installed
// All the below may come with the above include statement, but not sure if it is available only after 
// the above executes.
#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT/Adafruit_MQTT.h"

/************************* Adafruit.io Setup ******************************************/
#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883   // use 1883 for SSL
#define AIO_USERNAME    "IoTLabs"
#define AIO_KEY         "aio_REBO83agD50Mqh9VohnAzJcOK5ky"

/************ Global State (you don't need to change this!, per Brian so don't change it) ************/
TCPClient TheClient;

// Setup the MQTT client class by passing in the WiFi client, MQTT server, and login details
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY);

/****************************** Feeds ***************************************/ 
// Setup a feed called <object> for publishing. 
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>

//Adafruit_MQTT_Publish theTempuratureObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/Feed2_Tempurature");
Adafruit_MQTT_Publish theCurrentSensorOneStateObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/CurrentSensorOneState");
Adafruit_MQTT_Publish theCurrentSensorOneActualValue = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/CurrentSensorOneActualValue");
Adafruit_MQTT_Publish theCurrentSensorTwoStateObject = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/CurrentSensorTwoState");
Adafruit_MQTT_Publish theCurrentSensorTwoActualValue = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/CurrentSensorTwoActualValue");

Adafruit_MQTT_Publish sensorOneState = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.epilogone");
Adafruit_MQTT_Publish sensorOneValue = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.epilogonevalue");
Adafruit_MQTT_Publish sensorTwoState = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.epilogtwo");
Adafruit_MQTT_Publish sensorTwoValue = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/fuse.epilogtwovalue");


// Adafruit.io Set Up END

// For the neopixels: Pixel 0 is for sensor one, pixel 1 is for sensor two
const int PIN = 2; // Pin 2 on Argon for output signal to neopixels
const int NUMPIXELS = 2; // the actual number of neopixels
//Adafruit_NeoPixel thePixelObject(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
//supports WS2811/WS2812/WS2813
Adafruit_NeoPixel thePixelObject(NUMPIXELS, PIN, WS2812B);
//neopixel thePixelObject(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// Constructor: number of LEDs, pin number, LED type

const int pixelDelay=250; // just a variable I created to delay
const int startPixel=0; // the first pixel is 0 (zero based)
int var_PixelBrightness = 200; // the brightness value can go from 1 to 255
// thePixelObject.Color() takes RGB values from 0,0,0, to 255,255,255 - or a defined color from an included COLORS file
//int var_PixelColor = 0x008000;

// The following define the value ranges for the neopixels
// Green indicates an Epilog unit that is lasing (when the value is greater than the Yellow HIGH value)
// Yellow indicates an Epilog unit that is on but not lasing
// Red indicates an Epilog unit that is powered off (when the value is less than the Yellow LOW value)
float var_YellowValue_High = 0.99999;
float var_YellowValue_Low = 0.45000;

float var_SensorValueToPublish = 0.0;     //value read from sensor
float var_SensorActualValue = 0.0; // used to store the Actual value until it can be sent to the dashboard

// these are the variables used to store the power state of each Epilog laser unit
int var_PowerStateHoldOne;
int var_PowerStateHoldTwo;

// The broker's server may sever the connection if a publish event is not done prior to the default of 5 minutes.
// This variable will capture the elapsed time since last publish and 
// if that elapsed time >= 2 minutes, the program will ping the server.
int var_LastServerPing = 0;
int var_LastCurrentSensor1Read = 0;
int var_LastCurrentSensor2Read = 0;

float var_SensorMisreadValue = 16777.21;

// Start of github original Brian code

// PECMAC125A I2C address is 0x2A(42)
#define Addr 0x2A

byte data[36];
int typeOfSensor, maxCurrent, noOfChannel, i;
//float current = 0.0;
//unsigned int lastTime,last;

//SYSTEM_MODE(SEMI_AUTOMATIC);

// End of github original Brian code

//====================================================================================

SYSTEM_THREAD(ENABLED);

void setup() 
{
  // Put initialization like pinMode and begin functions here.
  Serial.begin(9600);
  //while(!Serial);
  waitFor(Serial.isConnected, 15000);
  delay(3000);
  Serial.println("The CurrentMonitor program has activated the Serial port.");

  pinMode(2,OUTPUT); // used for signal for neopixels

  thePixelObject.begin();
  thePixelObject.clear(); // sets all pixels' colors to 'off' - to initialize ALL pixels

  // Initiate the Wire library and join the I2C bus as a master or slave. This should normally be called only once.
  // address: the 7-bit slave address (optional); if not specified, join the bus as a master. 
  Wire.begin();

  //Connect to WiFi but not Particle Cloud
  WiFi.connect();
  while(WiFi.connecting()) 
     {
      Serial.printf(".");
       delay(250);
     }
  Serial.printf("\n");

  currentInit();  // call to initialize NCD board hosting current sensors

  // Request 6 bytes of data
  Wire.requestFrom(Addr, 6);

  // Read 6 bytes of data
  if (Wire.available() == 6) 
    {
      for(i=0;i<6;i++) 
        {
          data[i] = Wire.read();
        }
    }
  typeOfSensor = data[0];
  maxCurrent = data[1];
  noOfChannel = data[2];

  // Output data to Serial Monitor
  Serial.printf("Type of Sensor %i \n",typeOfSensor);
  Serial.printf("Max Current: %i \n", maxCurrent);
  Serial.printf("No. of Channels: %i \n", noOfChannel);
  delay(5000);
}

//====================================================================================

void loop() 
{
  MQTT_connect();

  // The default "keep-alive" timeframe is 5 minutes, or 300 seconds.
  // I will check for a publish event and if none has occured in the last 2
  // minutes, I will ping the server.
  if ((millis()-var_LastServerPing) > 120000) // (120 seconds)
    {
      Serial.println("Pinging the MQTT server");
      // ping the server to keep the mqtt connection alive
      if(! mqtt.ping()) // if I cannot ping the server, force a disconnect
         {
           Serial.println("Forcing an MQTT disconnect");
           mqtt.disconnect(); // forcing a disconnect will force a reconnection
           Serial.println("Attempting to reestablish MQTT connection");
         }
      var_LastServerPing = millis();
    }

  ReadSensorOne();
  ReadSensorTwo();

  delay(1); // just enough of a delay to allow completion of other tasks
}

//================================================================================

void ReadSensorOne()
{
    if((millis()-var_LastCurrentSensor1Read) > 15000) // wait 10 seconds before trying to read the sensor again
      {
       if(mqtt.Update()) 
          {
            var_SensorActualValue = getCurrent(Addr,1);
            Serial.printf("Current Value of Sensor One: %0.5f \n", var_SensorActualValue);
            
            if (var_SensorActualValue > var_YellowValue_High)
            // sensor reads an ON and LASING state
              {
                var_SensorValueToPublish = 1;
                ModifyNeoPixel(1, 0x00FF00); // make it green
              }
            
            if ((var_SensorActualValue <= var_YellowValue_High) && (var_SensorActualValue >= var_YellowValue_Low))
            // sensor reads an ON, but not LASING state
              {
                var_SensorValueToPublish = 1;
                ModifyNeoPixel(1, 0xFFFF00); // make it yellow
              }

            if (var_SensorActualValue < var_YellowValue_Low)
            // sensor reads an OFF state
              {
                var_SensorValueToPublish = 0;
                ModifyNeoPixel(1, 0xFF0000); // make it red
              }

            // This next code segment is for a sensor misread - the value would be greater than 16,000
            // so I set the Sensor value to 0 to stop the exhaust fan from coming on unnecessarily
            if (var_SensorActualValue == var_SensorMisreadValue) // sensor reads an ON state
              {
                var_SensorValueToPublish = 0;
                ModifyNeoPixel(1, 0xFF0000); // make it red
              }
            
            PublishToDashboard(1, var_SensorValueToPublish, var_SensorActualValue);
            //PublishToDashboard(1, var_SensorValue);
          } 
        var_LastCurrentSensor1Read = millis();
      }
  
}

//================================================================================

void ReadSensorTwo()
{
    if((millis()-var_LastCurrentSensor2Read) > 15000) // wait 10 seconds before trying to read the sensor again
      {
       if(mqtt.Update()) 
          {
            var_SensorActualValue = getCurrent(Addr,2);
            Serial.printf("               Current Value of Sensor Two: %0.5f \n", var_SensorActualValue);
            
            if (var_SensorActualValue > var_YellowValue_High)
            // sensor reads an ON and LASING state
              {
                var_SensorValueToPublish = 1;
                ModifyNeoPixel(0, 0x00FF00); // make it green
              }
            
            if ((var_SensorActualValue <= var_YellowValue_High) && (var_SensorActualValue >= var_YellowValue_Low))
            // sensor reads an ON, but not LASING state
              {
                var_SensorValueToPublish = 1;
                ModifyNeoPixel(0, 0xFFFF00); // make it yellow
              }

            if (var_SensorActualValue < var_YellowValue_Low)
            // sensor reads an OFF state
              {
                var_SensorValueToPublish = 0;
                ModifyNeoPixel(0, 0xFF0000); // make it red
              }
            
            // This next code segment is for a sensor misread - the value would be greater than 16,000
            // so I set the Sensor value to 0 to stop the exhaust fan from coming on unnecessarily
            if (var_SensorActualValue == var_SensorMisreadValue) // sensor reads an ON state
              {
                var_SensorValueToPublish = 0;
                
                ModifyNeoPixel(0, 0xFF0000); // make it red
              }
            
            PublishToDashboard(2, var_SensorValueToPublish, var_SensorActualValue);
            //PublishToDashboard(2, var_SensorValue);
          } 
        var_LastCurrentSensor2Read = millis();
      }
  
}

//======================================================================================

void ModifyNeoPixel(int theNeoPixel, int theColor)
{
   thePixelObject.setPixelColor(theNeoPixel,theColor);
   thePixelObject.setBrightness(var_PixelBrightness);       
   thePixelObject.show();  // Sends the updated pixel info out to the neopixels
}

//====================================================================================

void PublishToDashboard(int theObjectNumber, int theValue, float theActualValue)
//void PublishToDashboard(int theObjectNumber, int theValue)
{
  if(mqtt.Update())
      {
//      //Serial.println("mqtt.update is true");
        //theCurrentSensorOneStateObject.publish(theSensor1); 
        //theCurrentSensorTwoStateObject.publish(theSensor2);
        if (theObjectNumber==1)
          {
            // Particle.publish("Epilog1",String(theActualValue));
            // theCurrentSensorOneStateObject.publish(theValue);
            // theCurrentSensorOneActualValue.publish(theActualValue);
            sensorOneState.publish(theValue);
            sensorOneValue.publish(theActualValue);
          }
        else
          {
            if (theObjectNumber==2)
              {
                // theCurrentSensorTwoStateObject.publish(theValue);
                // theCurrentSensorTwoActualValue.publish(theActualValue);
                sensorTwoState.publish(theValue);
                sensorTwoValue.publish(theActualValue);
              }
          }
      }
}

//====================================================================================

// Function to connect and reconnect as necessary to the MQTT server.

void MQTT_connect() 
{
  int8_t ret;
 
  // Stop if already connected.
  if (mqtt.connected()) 
  {
    return;
  }
 
  Serial.print("Connecting to MQTT... ");
 
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.println(mqtt.connectErrorString(ret));
       Serial.println("Retrying MQTT connection in 5 seconds...");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
  }
  Serial.println("MQTT Connected!");
}

//===================================================================================

// Function to get Current reading from sensor
float getCurrent(int address, int i) 
{
  //int var_SensorIndex; // index for the arr_current array
  //float arr_current[2];

    // Start I2C Transmission
    Wire.beginTransmission(Addr);
    // Command header byte-1
    Wire.write(0x92);
    // Command header byte-2
    Wire.write(0x6A);
    // Command 1
    Wire.write(0x01);
    // Start Channel No.
    Wire.write(i);
    // End Channel No.
    Wire.write(i);
    // Reserved
    Wire.write(0x00);
    // Reserved
    Wire.write(0x00);
    // CheckSum
    Wire.write((0x92 + 0x6A + 0x01 + i + i + 0x00 + 0x00) & 0xFF);
    // Stop I2C Transmission
    Wire.endTransmission();
    delay(500);

    // Request 3 bytes of data
    Wire.requestFrom(Addr, 3);

    // Read 3 bytes of data
    // msb1, msb, lsb
    byte msb1 = Wire.read();
    byte msb = Wire.read();
    byte lsb = Wire.read();
    var_SensorActualValue = msb1<<16 | msb<<8 | lsb;

    // Convert the data to ampere
    var_SensorActualValue = var_SensorActualValue / 1000;
    return var_SensorActualValue;
}

//===================================================================================
// Initialize current sensor
void currentInit() 
{
  // Start I2C transmission
  Wire.beginTransmission(Addr);
  // Command header byte-1
  Wire.write(0x92);
  // Command header byte-2
  Wire.write(0x6A);
  // Command 2 is used to read no of sensor type, Max current, No. of channel
  Wire.write(0x02);
  // Reserved
  Wire.write(0x00);
  // Reserved
  Wire.write(0x00);
  // Reserved
  Wire.write(0x00);
  // Reserved
  Wire.write(0x00);
  // CheckSum
  Wire.write(0xFE);
  // Stop I2C transmission
  Wire.endTransmission();
}

Credits

Brian Rashap

Brian Rashap

10 projects • 46 followers
Former General Manager of US Facilities Operations at Intel Corporation. Currently loving my encore career as a teacher focused on IoT.
Clint Wolf

Clint Wolf

5 projects • 4 followers

Comments

Add projectSign up / Login