DAQ Arduino with NodeJS serialport

In this tutorial, I will make a Data Acquisition (DAQ) procedure between PC and Arduino. PC (with NodeJS) will send command to Arduino via serialport, then Arduino will send response to PC so that PC can plot the data.

At code line of Arduino, I make the main loop do nothing by using serialEvent to listen command. If command is “gene”, Arduino will send 2 parameters tempe_data and humid_data with JSON format. The reason to random generate 2 parameters is I am too lazy to connect the DHT22.

test_serialport_b.ino


/*
 * Date: Mon, 12/06/2017
 * Desc: Arduino and NodeJS serialport
 * Info: Random generator temperature and humidity
 * to simulate DHT22, JSON format
 * only response data when receive request
 */
// used for generating data
double tempe_data;
double humid_data;
// used for serial buffering
#define TERM_CHAR '\n'
#define BUF_LEN 16
int i = 0;
char incomingChar; 
char buf[BUF_LEN];
// used pinLED for test mode
int pinLED = 13;

void setup() {
 delay(1000);
 // Initialize serial port
 Serial.begin(9600);
 //Serial.begin(115200);

// Initialize random generator
 randomSeed(analogRead(3)*1234);

// Initialize pinLED for test mode
 pinMode(pinLED, OUTPUT);
 digitalWrite(pinLED, LOW);
}

void loop() {
 // do nothing
}

double generate_data(long offset, long min_range, long max_range){
 // data = offset + rand, for example if range = [0,4] offset will be 2, rand will be 2
 long randParam = random(min_range*100, max_range*100);
 double data = (double)offset + (double)randParam/100;
 return data;
}

// generate random temperature and humidity
// send with JSON format
void procedure(){
 // generate
 tempe_data = generate_data(2, -2, 2);
 humid_data = generate_data(3, -3, 3);
 // send
 Serial.print(F("{\"CH1\": "));
 Serial.print(tempe_data);
 // create a fake CH2 to test JSON
 Serial.print(F(", \"CH2\": "));
 Serial.print(humid_data);
 Serial.println(F("}"));
}

void serialEvent() {
 // when characters arrive over the serial port...
 if (Serial.available()) {
 // wait a bit for the entire message to arrive
 delay(100);
 // clean buffer and index
 memset(buf,'\0',BUF_LEN); 
 i = 0; 
 // read all the available characters
 while (Serial.available() > 0) {
 incomingChar = (char) Serial.read();
 if(incomingChar != TERM_CHAR && i != BUF_LEN) 
 buf[i++] = incomingChar;
 else
 break;
 delay(1); // wait for another byte 
 }
 // Activate function on right code
 if(String(buf) == "10"){
 digitalWrite(pinLED, HIGH);
 } else if(String(buf) == "20"){
 digitalWrite(pinLED, LOW);
 } else if(String(buf) == "gene"){
 procedure();
 } else{
 // to be continued
 } 
 }
}

If you ask what is the role of pinLED and command "10" and "20", it is for testing. What is the character 'F' in Serial.print(F("{\"CH1\": "))? It is saving buffering bytes for transmitting serial data. What is the flaw of the code? It still does not make Arduino sleep and wake up.

About NodeJS role, I use express, socket.io and ejs to make a web server. I use serialport to receive and transmit data. I use chart.js to plot the data. What is the flaw of the code? The DAQ only happens in my PC, it cannot be accessed by another PC or my smartphone. Here comes the code

index.js


var express = require("express");
var app = express();
app.use(express.static("public"));
app.set("view engine", "ejs");
app.set("views", "./views");

var server = require("http").Server(app);
var io = require("socket.io")(server);
server.listen(3000);

// Using serialport to communicate with COM3
var serialport = require("serialport");
var mySerial = new serialport("COM3", {
 baudrate: 9600,
 parser: serialport.parsers.readline("\n")
});

mySerial.on("open", function(){
 console.log("COM3 is opened.");
});
// close serialport
mySerial.on("close", function(){
 console.log("COM3 is closed.");
});
// listening data on CH1
mySerial.on("data", function(JSON_data){
 // var CH1_data = JSON.parse(JSON_data).CH1;
 // var CH2_data = JSON.parse(JSON_data).CH2;
 // console.log(CH1_data + "----" + CH2_data);
 // sending COM data to client
 // io.sockets.emit("com-data",CH1_data);
 io.sockets.emit("com-data",JSON_data);
});

// Host server// SocketIO listening for connection and disconnection
io.on("connection", function(socket){
 console.log("Connecting client id: " + socket.id);
 socket.on("disconnect", function(){
 console.log("Client id: " + socket.id + " has disconnected");
 });
});

// Service for loading
app.get("/", function(req, res){
 res.render("trangchu");
});

// Testing serialport transmit every 2 seconds
var auto_counter = 0;
setInterval(function(){
 console.log(auto_counter);
 //mySerial.write(String(auto_counter));
 mySerial.write("gene");
 mySerial.write("\n"); // Eliminate
 auto_counter++;
 if(auto_counter == 21){
 auto_counter = 0;
 }
},2000);

trangchu.ejs


<html>
<head>
<title>COM data</title>

http://jquery.js
http://socket.io/socket.io.js
http://js/Chart.js
http://js/Chart.min.js

</head>

<body>
COM3. This is my localhost 192.168.1.101:3000

<h2 id="noidung"></h2>
 
</body> //Chart.defaults.global.responsive = true; //var ctx = document.getElementById("myChart"); var ctx = document.getElementById("myChart"); var counter; var myLineChart; createGraph() //--- declare function function createGraph(){ counter = 0; myLineChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: ['Temperature'], data: [], backgroundColor: [ 'rgba(255, 99, 132, 0.2)' ], borderColor: [ 'rgba(255,99,132,1)' ], borderWidth: 1 }, { label: ['Humidity'], data: [], backgroundColor: [ 'rgba(54, 162, 235, 0.2)' ], borderColor: [ 'rgba(54, 162, 235, 1)' ], borderWidth: 1 }] }, options: { animation: { duration: 0, // general animation time }, scales: { yAxes: [{ ticks: { max: 6, beginAtZero:true }, stacked: false }] }, elements: { line: { tension: 0, // disables bezier curves } } } }); } // goi dien len server var socket = io("http://localhost:3000"); // client listening socket.on("com-data", function(data){ // $("#noidung").html(data); // console.log(counter + "-----" + data); var obj = JSON.parse(data); $("#noidung").html(obj.CH1 + " ----- " + obj.CH2); console.log(counter + "-----" + obj.CH1); counter++; myLineChart.data.labels.push(counter); myLineChart.data.datasets[0].data.push(obj.CH1); myLineChart.data.datasets[1].data.push(obj.CH2); myLineChart.update(); if(counter > 10){ myLineChart.destroy(); //counter = 0; createGraph(); } // if(counter = 20){ // counter = 0; // myLineChart.destroy(); // } else{ // counter++; // myLineChart.data.labels.push(counter); // myLineChart.data.datasets[0].data.push(data); // myLineChart.update(); // } }); $(document).ready(function(){ }); </html>

To use chart.js, in the folder public there will be a folder called js containing Chart.js and Chart.min.js (Chart will capital C). Note that I used COM3

Connect Arduino with loaded program and node index.js, you will see the result:

DAQ_nodejs

There is still some residual in my code. OK my bad. I am too lazy to delete the residual.

The biggest question in my head now is how to publish it globally so that everyone can see the chart of random generated parameters? I think about mongoDB and Heroku may work.

See you.

[PA-03c] Read temperature DHT21 with Qt

Another useful code from Vannevar Morgan, you can check these out:
youtube: https://www.youtube.com/watch?v=1PNn63P793Y

github: https://github.com/vannevar-morgan/Qt-Temperature-Sensor

However, I do not use the temperature DS18B20. I use temperature and humidity DHT21. And in Qt code, I use Celcius degree instead of Fahrenheit degree. In Arduino code, I use header file floatToString to display the data to LCD.

 pyqt_dht.ino


/*
 * Date: Wed, 10/05/2017
 * Desc: Read Temperature and Humidity from DHT21
 * Display on LCD
 * Send to PC via serial
 */
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include "floatToString.h"

#define I2C_ADDR 0x3F 
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

#define DHTPIN 9
#define DHTTYPE DHT21
DHT dht(DHTPIN, DHTTYPE);
float t;
float h;
char bufferstore[20]; // used by floatToString to display on LCD

void setup() {
 delay(1000);
 // Initialize Serial
 Serial.begin(9600); 
 // Initialize DHT
 dht.begin();

// Initialize LCD
 lcd.begin(16,2);
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH); 
}

void loop() {
 // Read sensor parameter
 h = dht.readHumidity();
 t = dht.readTemperature();
 // Display on LCD
 lcd_display_temp_humid(t,h);
 // Send via serialport
 Serial.print(t);
 Serial.print(",");
 Serial.flush();
 // Delay for next round
 delay(2000);
}

void lcd_display_temp_humid(float temperature, float humidity){
 lcd.clear();
 lcd.print("Tempe: ");
 lcd.print(floatToString(bufferstore, temperature, 2, 5, true));
 lcd.print(" C");
 lcd.setCursor(0,1);
 lcd.print("Humid: ");
 lcd.print(floatToString(bufferstore, humidity, 2, 5, true));
 lcd.print(" %");
}

floatToString.h


// floatToString.h
//
// Tim Hirzel
// tim@growdown.com
// March 2008
// float to string
// 
// If you don't save this as a .h, you will want to remove the default arguments 
// uncomment this first line, and swap it for the next. I don't think keyword arguments compile in .pde files

//char * floatToString(char * outstr, float value, int places, int minwidth=, bool rightjustify) {
char * floatToString(char * outstr, float value, int places, int minwidth=0, bool rightjustify=false) {
 // this is used to write a float value to string, outstr. oustr is also the return value.
 int digit;
 float tens = 0.1;
 int tenscount = 0;
 int i;
 float tempfloat = value;
 int c = 0;
 int charcount = 1;
 int extra = 0;
 // make sure we round properly. this could use pow from <math.h>, but doesn't seem worth the import
 // if this rounding step isn't here, the value 54.321 prints as 54.3209

// calculate rounding term d: 0.5/pow(10,places) 
 float d = 0.5;
 if (value < 0)
 d *= -1.0;
 // divide by ten for each decimal place
 for (i = 0; i < places; i++)
 d/= 10.0; 
 // this small addition, combined with truncation will round our values properly 
 tempfloat += d;

// first get value tens to be the large power of ten less than value 
 if (value < 0)
 tempfloat *= -1.0;
 while ((tens * 10.0) <= tempfloat) {
 tens *= 10.0;
 tenscount += 1;
 }

if (tenscount > 0)
 charcount += tenscount;
 else
 charcount += 1;

if (value < 0)
 charcount += 1;
 charcount += 1 + places;

minwidth += 1; // both count the null final character
 if (minwidth > charcount){ 
 extra = minwidth - charcount;
 charcount = minwidth;
 }

if (extra > 0 and rightjustify) {
 for (int i = 0; i< extra; i++) {
 outstr[c++] = ' ';
 }
 }

// write out the negative if needed
 if (value < 0)
 outstr[c++] = '-';

if (tenscount == 0) 
 outstr[c++] = '0';

for (i=0; i< tenscount; i++) {
 digit = (int) (tempfloat/tens);
 itoa(digit, &outstr[c++], 10);
 tempfloat = tempfloat - ((float)digit * tens);
 tens /= 10.0;
 }

// if no places after decimal, stop now and return

// otherwise, write the point and continue on
 if (places > 0)
 outstr[c++] = '.';


 // now write out each decimal place by shifting digits one by one into the ones place and writing the truncated value
 for (i = 0; i < places; i++) {
 tempfloat *= 10.0; 
 digit = (int) tempfloat;
 itoa(digit, &outstr[c++], 10);
 // once written, subtract off that digit
 tempfloat = tempfloat - (float) digit; 
 }
 if (extra > 0 and not rightjustify) {
 for (int i = 0; i< extra; i++) {
 outstr[c++] = ' ';
 }
 }


 outstr[c++] = '\0';
 return outstr;
}

Till now, I am just a copier without clearly understand how things are done. I have temperature and humidity but only show one of them. The main loop of Arduino has to send the data continuously. It is not practical. The right procedure is when PC sends command to Arduino, Arduino responses. I hope to do better job next time.

PS: remember 10k pick-up resistor of DHT signal pin.

[Dimmer] Amplified current feedback

Opamp 741 negative feedback pot 503 over 1k2 input resistor.

Negative direction load current.

Average.h 200 samples, Timer 1 frequency 50Hz.

Need noise filter.

Next time I will use digital potentiometer instead of pot 503 and automatic gain adjust algorithm.

acs712_amplifier_741

R3 = 1k2, R4 is replaced with POT 503 and use Op-amp 741

[Code] c_dimmer_timer.ino


/*
 * Date: Tue, 25/04/2017
 * Desc: Serving for mono_dimmer 
 * Info: PWM on pin 5, positive logic [255] = brightest, [0] = off
 * Timer 0 is used for pin 5 PWM
 * Timer 1 is used for get adc sampling time with Ts = 20ms (for 50Hz)
*/
#include <Average.h>
// Setting array for calculation
int lengthArray = 200;
Average<float> ave_nor(lengthArray);
Average<float> ave_amp(lengthArray);

int startMark = 0;
const int ledPin = 5;

int value = 150;
void setup()
{
 //pinMode(ledPin, OUTPUT);
 setPwmFrequency(5, 8);
 pinMode(2, INPUT);
 Serial.begin(9600);
 Serial.println("On going");

analogWrite(ledPin, value);
 // Using timer 1 interrupt
 noInterrupts(); // disable all interrupts
 TCCR1A = 0;
 TCCR1B = 0;
 //TCNT1 = 16242; // preload timer 65536-16MHz/256/1Hz
 //TCNT1 = 34286; // preload timer 65536-16MHz/256/2Hz
 TCNT1 = 64286; // preload timer 65536-16MHz/256/50Hz
 TCCR1B |= (1 << CS12); // 256 prescaler 
 TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
 interrupts(); // enable all interrupts
}

ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
 TCNT1 = 64286; // preload timer
 // Get the data;
 ave_nor.push(analogRead(A2));
 ave_amp.push(analogRead(A1));
 startMark++;
 if (startMark == lengthArray){
 float sensorValue_a = ave_amp.mean();
 float sensorValue_n = ave_nor.mean();
 Serial.print(sensorValue_a);
 Serial.print("-----");
 Serial.println(sensorValue_n);
 // reset
 startMark = 0;
 } 
}

void loop ()
{
 if (Serial.available()) {
 char ser = Serial.read(); //read serial as a character
 
 //NOTE because the serial is read as “char” and not “int”, the read value must be compared to character numbers
 //hence the quotes around the numbers in the case statement
 
 switch (ser)
 {
 case '0':
 value = 0; 
 break;
 
 case '1':
 value = 100; 
 break;
 
 case '2':
 value = 130; 
 break;
 
 case '3':
 value = 145; 
 break;
 
 case '4':
 value = 160; 
 break;

case '5':
 value = 175; 
 break;

case '6':
 value = 190; 
 break;

case '7':
 value = 210; 
 break;

case '8':
 value = 230; 
 break;

case '9':
 value = 255; 
 break;
 
 default:
 //Serial.println("Invalid entry");
 break;
 }
 }
 analogWrite(ledPin, value);
 //
}

void setPwmFrequency(int pin, int divisor) {
 byte mode;
 if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
 switch(divisor) {
 case 1: mode = 0x01; break;
 case 8: mode = 0x02; break;
 case 64: mode = 0x03; break;
 case 256: mode = 0x04; break;
 case 1024: mode = 0x05; break;
 default: return;
 }
 if(pin == 5 || pin == 6) {
 TCCR0B = TCCR0B & 0b11111000 | mode;
 } else {
 TCCR1B = TCCR1B & 0b11111000 | mode;
 }
 } else if(pin == 3 || pin == 11) {
 switch(divisor) {
 case 1: mode = 0x01; break;
 case 8: mode = 0x02; break;
 case 32: mode = 0x03; break;
 case 64: mode = 0x04; break;
 case 128: mode = 0x05; break;
 case 256: mode = 0x06; break;
 case 1024: mode = 0x07; break;
 default: return;
 }
 TCCR2B = TCCR2B & 0b11111000 | mode;
 }
}