Управляем электроникой через Интернет с помощью NodeJS, SQL и сайта

В двух словах - это проект о том, как можно управлять микроконтроллером через веб-интерфейс (интернет).

Мы уже касались данной темы в рубрике "Умный дом". Но сегодня мы остановимся подробнее на том, как контролировать любое электрическое устройство из любого места на Земле с помощью Интернета. Хотелось, чтобы этот проект расширил понимание программирования, веб-разработки и того, как лучше сделать простой, но эффективный проект.

Аппаратное обеспечение поддерживается минимально, поэтому можно больше сосредоточиться на программном обеспечении. Поэтому мы будем иметь дело с простым светодиодом и потенциометром.

Потенциометр отправит данные, а светодиод их получит (яркость, широтно-импульсная модуляция (PWM)). Используя NodeJS, последовательные данные будут прочитаны (значение потенциометра) и записаны (яркость светодиода). Трудная часть этого проекта заключалась в получении входных данных из удаленного места (веб-сервера).

Компоненты и программы

Аппаратное обеспечение:

  • Arduino Uno, Nano, Mega (большинство плат с последовательным подключением будут работать);
  • Один светодиод и резистор ограничения тока. Используйте наш калькулятор, если вы в чем-то не уверены - Калькулятор Ома;
  • 10K потенциометр.

Программное обеспечение:

  • Arduino IDE;
  • Node.JS (это программное обеспечение на компьютере, очень простое в установке);
  • MySQL сервер (самый простой способ - найти дешевый веб-хостинг и вы также сможете получить бесплатные доменные имена).

Используемые языки программирования и скрипты:

Arduino (измененный C/C++), JavaScript (Nodejs), PHP, HTML и CSS.

Логика программного обеспечения: системная архитектура

Потенциометр

Начинать нужно с Arduino, чтение значения потенциометра в последовательном мониторе. Однако на этот раз мы будем использовать Node.JS для чтения значения. NodeJS откроет последовательную связь с тем же портом к которому подключена Arduino, и прочитает значение потенциометра.

Затем NodeJS загрузит данные в удаленную базу данных SQL, это будет происходить каждый раз, когда будет выведено новое значение. Веб-страница будет подключаться через определенные интервалы к базе данных SQL и будет извлекать значение потенциометра. Затем это будет отображаться на веб-странице.

Светодиод

Для светодиодов яркость PWM будет установлена ​​пользователем на удаленной веб-странице. Входные данные сохраняются в базу данных SQL, через каждый установленный интервал база данных проверяется на изменения в PWM, это выполняется NodeJS. Если значение отличается от предыдущего значения, новое значение будет отправлено в Arduino через последовательную шину. Arduino изменяет значение выходного PWM светодиода, чтобы изменить его яркость.

Калькулятор закона Ома использует формулы:

V = IR

P = IV = I²R = V²/R

Для этого проекта будет использоваться синий светодиод. Это важно, поскольку по мере увеличения частоты света падение напряжения также увеличивается.

Поскольку синий светодиод имеет более высокую частоту по сравнению с чем-то вроде красного светодиода. Это означает более высокое прямое напряжение. В зависимости от типа и размера рабочий диапазон будет отличаться. Для проекта использован резистор 220 Ом последовательно, отрицательная нога на землю и положительная на ШИМ (PWM) пин на Arduino.

Потенциометр подключен к аналоговому выходу. 5VCC на один конец, GND на другой и средний вывод подключен к аналоговому выводу (A0 в нашем случае). См. ниже.

Схема соединения

Здесь всё достаточно просто: подключите свой токоограничивающий резистор последовательно со светодиодом, убедитесь, что вы правильно всё соединили. Один конец пойдет на GND (земля), а другой конец на пин Arduino. В нашем случае мы использовали пин 12 для светодиода и A7 для потенциометра. Вы можете сделать по-своему или аналогично схеме представленной выше.

Код для Ардуино

Во-первых, светодиод и потенциометр были проверены, работают ли они как ожидалось. Это делается простой программой, в которой значение потенциометра контролирует светодиод. Была использована функция ограничения для изменения диапазона вместо 0-1023 до 0-255, но простое деление на 4 (".../4") работает. Значение потенциометра было сглажено, взято среднее значение из 10 последовательных показаний, это для удаления пиков. Однако это сглаживание вызвало проблемы с NodeJS, так что это было удалено позже из проекта.

//--------Global Const---------------------
const int pot = A7;
const int led = 12;
//-----------------------------------------

//---------Smoothing Pot Val --------------
//Mean average deafult set to 10
const int numbReadings = 10;
int total;
int averagePot;
int previousPotVal = 0;
int counter = 0;
int arrayOfReadings[numbReadings]; //array list to store values
//-----------------------------------------

void setup() {
  pinMode(pot, INPUT);
  pinMode(led, OUTPUT);
  Serial.begin(9600);

  //Initilazlised all reading in array to be 0
  for (int x = 0; x < numbReadings; x++) {
    arrayOfReadings[x] = 0;
  }
  delay(3000);
}

void loop() {
   runingPotAvg(); // Calls runningPotAvg function
   ledControl(); // Calls ledControl function

}

void runingPotAvg () {
  total -= arrayOfReadings[counter]; //Deletes the previous reults
  arrayOfReadings[counter] = analogRead(pot); 
  total += arrayOfReadings[counter];
  averagePot = total / numbReadings;
  counter++;
  if (counter >= numbReadings) {
    counter = 0;
  }
}

void ledControl() {
  ledBrightness = constrain(averagePot, 0, 255);
  analogWrite(led, ledBrightness);
}

Чтение / запись

Следующим шагом было получение данных через последовательный монитор, предоставляемый Arduino IDE для установки яркости. Для этого используется serial.parseInt(), который принимает целочисленное значение и игнорирует строки. Кроме того, в код добавляется проверка ошибок. Допустимый диапазон значения PWM равен 0 - 255, когда пользователь вводит> 255, тогда он присваивает значение 255, и если пользователь вводит значение < 0, то присваивает 0. Значение потенциометра выводится на последовательном мониторе, однако это происходит только когда текущее значение > или < +/- 5. Это было сделано для того, чтобы сделать чтение более стабильным, так как оно колебалось.

void readSerialPort() {
  // send data only when you receive data:
  if (Serial.available() > 0) {

    // read the incoming integer:
    serialRead = Serial.parseInt();

    // say what you got for debugging only
    // Serial.print("LED Value: ");
    // Serial.println(serialRead);
  }
  if (serialRead > 255) {
    ledBrightness = 255;
  }
  else if (serialRead < 0) {
    ledBrightness = 0;
  }
  else {
    ledBrightness = serialRead;
  }
}

//slight change to ledControl() function -->
void ledControl() {
  //ledBrightness = constrain(averagePot, 0, 255);
  analogWrite(led, ledBrightness);
}

void sendPotVal() {
  if (averagePot < previousPotVal - 5 || averagePot > previousPotVal + 5) {
    previousPotVal = averagePot;
    Serial.println(averagePot);
    delay(2000);
  }
}
//the 2 new function needs to be called in the loop function, like wise
void loop() {
  runingPotAvg(); // Calls runningPotAvg function
  sendPotVal(); // Calls sendPotVal function
  readSerialPort(); // Calls readSerialPort function
  ledControl(); // Calls ledControl function

}

NodeJS

На всякий случай - мы не сможем показать вам здесь как установить и настроить SQL-сервер, есть множество учебных пособий.

Существует 3 основных аспекта программ NodeJS:

  • Чтение последовательных данных
  • Запись последовательных данных
  • Обновление базы данных SQL

Чтобы выполнить последовательное соединение в NodeJS, необходимо загрузить модуль с именем serialport, можно выполнить с помощью команды npm. Откройте CMD в папке, где будет храниться программа NodeJS, установите, набрав: npm install serialport.

Также необходимо установить модуль SQL, чтобы иметь возможность подключаться к базе данных sql: npm install mysql.

NodeJS - Последовательный порт

Первым шагом с NodeJS было прочитать данные и отправить яркость pwm в Arduino. Это было сделано путем открытия последовательного соединения с той же скоростью и портом. После установления соединения читаем входящие сообщения и выводим их в окне консоли. Проблема возникла, когда была попытка записать значение pwm для управления яркостью. Ошибка: Port Not Open.

Первоначальное решение заключалось в том, чтобы вызвать функцию записи, когда есть входные данные. Однако это был плохой костыль, и решение было так себе, хотя работало, и отправляло только когда значение потенциометра было изменено. Код примера для последовательного модуля не работал, либо показывал ту же ошибку. Позже стало понятно, что программа пыталась выполнить функцию записи, не открывая порт, что привело к этой ошибке. Столкнуться с этой проблемой можно, используя функцию setInterval().

// include the various libraries that you'll use:
var myPort = require('serialport');       // includes the serialport library
var ledBrightness = '0';
var newValue = true;

//---------- New Serial Port Instance ---------
//Communication speed must match arduino setting. 

// configure the serial port:
	//SerialPort = serialport.SerialPort,       // make a local instance of serialport
    portName = process.argv[2],                 // get serial port name from the command line               
var serialOptions = {                           // serial communication options
      baudRate: 9600,                           // data rate: 9600 bits per second
      parser: delimiterparser: myPort.parsers.readline("\n") // newline generates a data event
    };


// If no port name was defined then show message and excit the program
if (typeof portName === "undefined") {
  console.log("You need to specify the serial port, like so:\n");
  console.log("    node myscript.js portname");
  process.exit(1);
}
// open the serial port:
var myPort = new myPort(portName, serialOptions);

// set up event listeners for the serial events:
myPort.on('open', showPortOpen);
myPort.on('data', sendSerialData);
myPort.on('close', showPortClose);
myPort.on('error', showError);


function readSQL(data){
	con.query(data, function (err, result) {
		if (err) throw err;
		console.log(result.affectedRows + " record(s) updated");
  });
}

function writeSQL(data){
	con.query(data, function (err, result, fields) {
    if (err) throw err;
    //console.log(result[0].LedPWM);
	if(ledBrightness != result[0].LedPWM){
		ledBrightness = result[0].LedPWM.toString();
		newValue = true;
		console.log("New Brightness: "+ledBrightness);
	}
  });
}

// this is called when the serial port is opened:
function showPortOpen() {
  console.log('port open. Data rate: ' + myPort.options.baudRate);
}

// this is called when new data comes into the serial port:
function sendSerialData(data) {
  console.log("PotValue: "+ Number(data));
}

function showPortClose() {
   console.log('port closed.');
}
// this is called when the serial port has an error:
function showError(error) {
  console.log('Serial port error: ' + error);
}

function sendToSerial(data) {
  myPort.write(data);
}

//-------------------------------------------- Main Loop ------------------------
function mainLoop(){
	sendToSerial('255'); //sends a pwm value of 255 in string form to set led brightness to 100%
}

setInterval(mainLoop, 5000);  // Will call mainLoop() every 5 Seconds (default)

NodeJS - MySQL

Библиотека MySQL использовалась (npm install MySQL) для подключения к базе данных SQL. Для удаленного сервера вместо локального хоста (localhost) использовался IP-адрес сервера.

var con содержит информацию о соединении в формате JSON, как только соединение успешно выполнено, к базе данных можно обратиться. Были созданы 2 функции - одна для обновления таблицы и другая для работы с параметрами, взятыми в SQL-запросе. Таблица обновлений вызывается, когда принимается новое значение потенциометра, и запрос проверки яркости будет выполняться периодически.

/*
  Coded by Masum Ahmed www.mahmed.tech for NodeJs <---> Arduino Communication Project
  Date: 12/07/2017
  Version: 1.00
*/
//-------------------------- Program ------------------------------
// include the various libraries that you'll use:
var myPort = require('serialport');  // include the serialport library
var mysql = require('mysql');		// include the mysql library
var ledBrightness = '0';
var newValue = true;
var portOpen = false;

// ------ SQL Databse Details -----------------
  var con = mysql.createConnection({
  host: "Server IP",
  user: "Your_Username",
  password: "Your_Passowrd",
  database : "Database_Name"
});
//---------------------------------------------

//----------- Make SQL Connection -------------
con.connect(function(err, result) {
  if (err) throw err;
  console.log("Connected to SQL Database");
});
//---------------------------------------------

//Communication speed must match arduino setting. 
// Serial port configuration:
	//SerialPort = serialport.SerialPort,             // make a local instance of serialport
    portName = process.argv[2],                 // get serial port name from the command line
    delimiter = 'n';                // serial parser to use, from command line
var serialOptions = {                           // serial communication options
      baudRate: 9600,                           // data rate: 9600 bits per second
      parser: delimiter // newline generates a data event
    };

// if the delimiter is n, use readline as the parser:
if (delimiter === 'n' ) {
  serialOptions.parser = myPort.parsers.readline('\n');
}

if (typeof delimiter === 'undefined') {
  serialOptions.parser = null;
}

// If the user didn't give a serial port name, exit the program:
if (typeof portName === "undefined") {
  console.log("You need to specify the serial port, like so:\n");
  console.log("    node myscript.js portname");
  process.exit(1);
}

//---------- New Serial Port Instance ---------
var myPort = new myPort(portName, serialOptions);
//---------------------------------------------


// set up event listeners for the serial events:
myPort.on('open', showPortOpen);
myPort.on('data', sendSerialData);
myPort.on('close', showPortClose);
myPort.on('error', showError);


function readSQL(data){
	con.query(data, function (err, result) {
		if (err) throw err;
		console.log(result.affectedRows + " record(s) updated");
  });
}

function writeSQL(data){
	con.query(data, function (err, result, fields) {
    if (err) throw err;
    //console.log(result[0].LedPWM);
	if(ledBrightness != result[0].LedPWM){
		ledBrightness = result[0].LedPWM.toString();
		newValue = true;
		console.log("New Brightness: "+ledBrightness);
	}
  });
}

// this is called when the serial port is opened:
function showPortOpen() {
  console.log('port open. Data rate: ' + myPort.options.baudRate);
  //portOpen = true;
}

// this is called when new data comes into the serial port:
function sendSerialData(data) {
  writeQuery("UPDATE ArduinoJS SET Value = '"+data+"' WHERE ID = 1");
  console.log("PotValue: "+ Number(data));
}

function showPortClose() {
   console.log('port closed.');
}
// this is called when the serial port has an error:
function showError(error) {
  console.log('Serial port error: ' + error);
}

//This function is called to write data to serial port
function sendToSerial(data) {
  myPort.write(data);
}

//Makes a update (write) query to the sql table
function writeQuery(querya){
	con.query(querya, function (err, result) {
		if (err) throw err;
		console.log(result.affectedRows + " record(s) updated");
  });
}

//makes a select query on a sql table
function readQuery(queryb){
	con.query(queryb, function (err, result, fields) {
    if (err) throw err;
    //console.log("SQL LED PWM Value: "+result[0].LedPWM);  //for debugging
	//will only write to serial if the value changes from the previous stored brightness aka user has inputed new pwm value
	if(ledBrightness != result[0].LedPWM){  
		console.log("\n  Old Brightness: "+ledBrightness+"\n  New Brightness: "+result[0].LedPWM.toString()+"\n");
		//The results is in a Array hence this notation this can be seen printing the results directly 
		ledBrightness = result[0].LedPWM.toString();
		sendToSerial(ledBrightness);
	}
  });
}
//-------------------------------------------- Main Loop ------------------------
function mainLoop(){
	readQuery("SELECT LedPWM FROM ArduinoJS WHERE ID = 1");  //selects pwm value 
}

setInterval(mainLoop, 5000);  // Will call mainLoop every 5 Seconds (default)

Веб-интерфейс

Основная веб-страница была написана на PHP. Для отображения данных sql используется таблица html и form.

<?php
//all of this code used for refreshing the page
$page = $_SERVER['PHP_SELF'];
$sec  = "12";
?>
<?php
$servername = "localhost";
$username   = "Your_username";
$password   = "Your_password";
$dbname     = "Your_database";

// Create connection
$conn = mysqli_connect($servername, $username, $password, $dbname);
// Check connection
if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}

$sql    = "SELECT * FROM ArduinoJS";
$result = $conn->query($sql);

//print some nice text at the top
echo "<h1><span class='blue'><</span>NodeJs - Ardunio  <span class='blue'>></span> <span class='yellow'>Communication Project</pan></h1>";
echo "<h2>Made By <a rel='nofollow' rel='noreferrer' href='http://www.mahmed.tech' target='_blank'>Masum Ahmed</a></h2>";

//draw the table
echo "<table class = 'container'>
<thread>
<tr>
<th><h1>ID</h1></th>
<th><h1><h1>LED PWM</h1></th>
<th><h1>POT VALUE</h1></th>
</tr></thread>";
//loop through the table and print the data into the table
while ($row = mysqli_fetch_array($result)) {
    
    echo "<tbody><tr>";
    $unit_id = $row['ID'];
    echo "<td>" . $row['ID'] . "</td>";
    $column      = "LedPWM";
    $current_LED = $row['LedPWM'];
    echo "<td><form action= change_SQL.php method= 'post'>
<input type='text' name='ledpwmval' value=$current_LED >
<input type='hidden' name='unit' value= $unit_id >
<input type='hidden' name='column' value= $column >
<input type= 'submit' name= 'change_but' style='text-align:center' value='Change LED Brightness'></form></td>";
    echo "<td>" . $row['Value'] . "</td>";
    
} //while

echo "</tr><tbody></table>";
?>

CSS стили и Javascript

Добавляем CSS стили и JS:

<style>
@charset "UTF-8";
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,700);
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,700);
body {
  font-family: 'Ubuntu', Open Sans, sans-serif;
  font-weight: 300;
  line-height: 1.42em;
  color:#A7A1AE;
  background-color:#1F2739;
}
h1 {
  font-size:3em; 
  font-weight: 300;
  line-height:1em;
  text-align: center;
  color: #4DC3FA;
}
h2 {
  font-size:1em; 
  font-weight: 300;
  text-align: center;
  display: block;
  line-height:1em;
  padding-bottom: 2em;
  color: #FB667A;
}
h2 a {
  font-weight: 700;
  text-transform: uppercase;
  color: #FB667A;
  text-decoration: none;
}
.blue { color: #185875; }
.yellow { color: #89f442; }
.container th h1 {
    font-weight: bold;
    font-size: 2em;
    text-align: left;
  color: #185875;
}
.container td {
      font-weight: normal;
      font-size: 1.8em;
  -webkit-box-shadow: 0 2px 2px -2px #0E1119;
       -moz-box-shadow: 0 2px 2px -2px #0E1119;
            box-shadow: 0 2px 2px -2px #0E1119;
}
.container {
      text-align: left;
      overflow: hidden;
      width: 80%;
      margin: 0 auto;
  display: table;
  padding: 0 0 8em 0;
}
.container td, .container th {
      padding-bottom: 2%;
      padding-top: 2%;
      padding-left:2%;  
}
/* Background-color of the odd rows */
.container tr:nth-child(odd) {
      background-color: #323C50;
}
/* Background-color of the even rows */
.container tr:nth-child(even) {
      background-color: #2C3446;
}
.container th {
      background-color: #1F2739;
}
.container td:first-child { color: #FB667A; }
.container tr:hover {
   background-color: #464A52;
-webkit-box-shadow: 0 6px 6px -6px #0E1119;
       -moz-box-shadow: 0 6px 6px -6px #0E1119;
            box-shadow: 0 6px 6px -6px #0E1119;
}
.container td:hover {
  background-color: #1F2739;
  color: #403E10;
  font-weight: bold;
  
  box-shadow: #7F7C21 -1px 1px, #7F7C21 -2px 2px, #7F7C21 -3px 3px, #7F7C21 -4px 4px, #7F7C21 -5px 5px, #7F7C21 -6px 6px;
  transform: translate3d(6px, -6px, 0);
  
  transition-delay: 0s;
      transition-duration: 0.4s;
      transition-property: all;
  transition-timing-function: line;
    }
    form {
  background: none;
  margin: 0px 5% 0;
  padding: 0px;
  width: 360px;
}
input {
  display: inline-block;
  font-size: 1em;
  width: 310px;
  min-width: 100px;
  margin: 10px 10px;
  padding: 10px 8px 10px 8px;
  border-radius: 5px;
  box-shadow: inset 0 1px 2px rgba(0,0,0, .55), 0px 1px 1px rgba(255,255,255,.5);
  border: 1px solid #666;
}
input {
  opacity: 0.5;
}
input:hover,
input:focus {
  opacity: .7;
  color:#08c;
  border: 1px solid #08c;
   box-shadow: 0px 1px 0px rgba(255,255,255,.25),inset 0px 3px 6px rgba(0,0,0,.25);
}
input[type="text"]:focus,
input[type="password"]:focus {
  box-shadow: inset 0 1px 2px rgba(255,255,255, .35), 0px 1px 15px rgba(0,246,255,.5);
  border: 1px solid #08c;
  outline: none;
}
input[type="submit"] {
  font-size: 1em;
  appearance: none;
  opacity: .90;
  width: auto;
  background: #08c;
  box-shadow: inset 0 1px 2px rgba(255,255,255, .35), 0px 1px 6px rgba(0,246,255,.5);
  border: 1px solid #0a5378;
  border-radius: 4px;
  color: #eee;
  cursor: pointer;
  text-shadow:0px -1px 0px rgba(0,0,0,.5);
}
input[type="submit"]:hover {
  background: #08c;
  width:Auto;
  border: 1px solid #0a5378;
  border-radius: 3px;
  box-shadow: inset 0px 3px 16px rgba(0,0,0,.25),0px 1px 10px rgba(255,255,255,.5),inset 0px -1px 2px rgba(255,255,255,.35);
  text-shadow:0px 1px 1px rgba(0,0,0,.65);
  -webkit-transition: all 0.40s ease-out;
  transition: all 0.40s ease-out;
}
}
    </style>
    <script>
  window.console = window.console || function(t) {};
</script>
  <script>
  if (document.location.search.match(/type=embed/gi)) {
    window.parent.postMessage("resize", "*");
  }
</script>

Итоговый результат

На видео ниже показан процесс работы с проектом.

На этом всё. Желаем вам отличных проектов и побольше новых изобретений.

Ардуино+