Armazenando dados na nuvem com WORDPRESS
O projeto IoT exige que os dados sejam armazenados em nuvem.
Apresentarei uma solução baseada em WordPress, que atende esta demanda.
O WordPress é um software de gestão de conteúdo online, com ele é possivel criar Sites e gerir artigos, como este que estou fazendo.
Plugins são componentes adicionados ao wordpress que permite incluir recursos que se deseja controlar no wordpress.
O Fonte abaixo, é um plugin no wordpress, que está contido no projeto Geiser.
GIT do projeto:
https://github.com/marcelomaurin/GIESER
Para explicar irei apresentar o fonte do plugin e comentar o mesmo.
Plugin do wordpress
Fonte do Plugin index.php:
<?php
/**
* Plugin Name: Geiser
* Plugin URI: http://maurinsoft.com.br/geiser
* Description: Gestão de Contador Geiser
* Version: 1.0
* Author: Marcelo Maurin Martins
* Author URI: https://meusite.com
*/
// Cria as tabelas quando o plugin for ativado
register_activation_hook( __FILE__, 'criar_tabelas' );
function criar_tabelas() {
global $wpdb;
// Cria a tabela geiser_leitores
$table_name1 = $wpdb->prefix . 'geiser_leitores';
$charset_collate = $wpdb->get_charset_collate();
$sql1 = "CREATE TABLE $table_name1 (
id mediumint(9) NOT NULL AUTO_INCREMENT,
nome varchar(50) NOT NULL,
token varchar(50) NOT NULL,
status TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql1 );
// Cria a tabela geiser_logs
$table_name2 = $wpdb->prefix . 'geiser_logs';
$sql2 = "CREATE TABLE $table_name2 (
id mediumint(9) NOT NULL AUTO_INCREMENT,
token varchar(50) NOT NULL,
lastdt datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
usvh float NOT NULL,
temp float NOT NULL,
hum float NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
dbDelta( $sql2 );
}
function geiser_registra_log_endpoint( WP_REST_Request $request ) {
$file_path = trailingslashit(ABSPATH) . 'ws/registra_log.php';
$response = wp_remote_post( get_site_url() . '/' . $file_path, array( 'timeout' => 120 ) );
return $response['body'];
}
// Adiciona a rota do web service para chamar o arquivo ./ws/registra_log.php
add_action( 'rest_api_init', 'geiser_register_api_routes' );
add_action( 'rest_api_init', 'registrar_rota2_personalizada');
function geiser_register_api_routes() {
//...
register_rest_route( 'geiser/v1', '/registra_log', array(
'methods' => 'GET',
'callback' => 'geiser_registra_log_endpoint',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
}
) );
}
// Endpoint do web service para chamar o arquivo ./ws/registra_log.php
/*
function geiser_registra_log_endpoint( WP_REST_Request $request ) {
// Inclui o arquivo ./ws/registra_log.php
require_once( dirname( __FILE__ ) . '/ws/registra_log.php' );
}
*/
// Adiciona o menu no painel de administração
add_action( 'admin_menu', 'geiser_admin_menu' );
function geiser_admin_menu() {
add_menu_page( 'Geiser Admin', 'Geiser Admin', 'manage_options', 'geiser-admin', 'geiser_admin_page', 'geiser_admin_page' );
add_submenu_page( 'geiser-admin', 'Dispositivos', 'Dispositivos', 'manage_options', 'geiser_dispositivos_page', 'geiser_dispositivos_page' );
add_submenu_page( 'geiser-admin', 'Logs', 'Logs', 'manage_options', 'geiser_logs_page', 'geiser_logs_page' );
add_submenu_page( 'geiser-admin', 'Análise', 'Análise', 'manage_options', 'geiser_analise_page', 'geiser_analise_page' );
// Adiciona Chart.js ao painel de administração
add_action('admin_enqueue_scripts', function () {
wp_enqueue_script('chartjs', 'https://cdn.jsdelivr.net/npm/chart.js');
});
}
add_action('rest_api_init', 'registrar_rota_personalizada');
function registrar_rota_personalizada() {
register_rest_route('Geiser/v1', '/registro.php', [
'methods' => 'GET',
'callback' => 'geiser_lst_callback',
]);
}
function geiser_lst_callback(WP_REST_Request $request) {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_logs';
// Obtenha os parâmetros do corpo da solicitação
$data_inicio = $request->get_param('data_inicio');
$data_fim = $request->get_param('data_fim');
$ip = $request->get_param('ip');
// Construa a consulta SQL
$sql = "SELECT * FROM {$table_name}";
$where_clauses = array();
if (!empty($data_inicio)) {
$where_clauses[] = $wpdb->prepare('lastdt >= %s', $data_inicio);
}
if (!empty($data_fim)) {
$where_clauses[] = $wpdb->prepare('lastdt <= %s', $data_fim);
}
if (!empty($ip)) {
$where_clauses[] = $wpdb->prepare('ip = %s', $ip);
}
if (!empty($where_clauses)) {
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
}
$sql .= ' ORDER BY lastdt DESC';
// Execute a consulta e obtenha os resultados
$logs = $wpdb->get_results($sql);
// Retorne os resultados como uma resposta JSON
return new WP_REST_Response($logs, 200);
}
function registrar_rota2_personalizada() {
register_rest_route('Geiser/v1', '/registro.php', [
'methods' => 'POST',
'callback' => 'geiser_reg_callback',
]);
}
function geiser_reg_callback(WP_REST_Request $request) {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_logs';
$usvh = $request->get_param('usvh');
$temp = $request->get_param('temp');
$hum = $request->get_param('hum');
// Obtenha o IP do cliente
$ip = $_SERVER['REMOTE_ADDR'];
//echo $ip;
//echo "<br/>";
$sql = "SELECT * FROM {$wpdb->prefix}geiser_leitores WHERE token = '$ip'";
//echo $sql;
//echo "<br/>";
// Verificar se o dispositivo está cadastrado na tabela geiser_leitores
$device = $wpdb->get_row($sql);
//echo $device;
//echo "<br/>";
if ($usvh === null)
{
return new WP_REST_Response(array('error' => 'usvh nao definido'), 404);
}
if ($device === null) {
return new WP_REST_Response(array('error' => 'Device nao cadastrado'), 404);
}
// Obtenha a data e hora atual do sistema
$lastdt = date('Y-m-d H:i:s');
// O campo status deve ser sempre 1
$status = 1;
// Insira os dados na tabela
$wpdb->insert(
$table_name,
array(
'usvh' => $usvh,
'temp' => $temp,
'hum' => $hum,
'token' => $ip
),
array('%s', '%s', '%s', '%s', '%d', '%d', '%s')
);
// Retorne uma resposta JSON com uma mensagem de sucesso
return new WP_REST_Response(array('message' => 'Registro inserido com sucesso!'), 200);
}
function geiser_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_leitores';
// Processa a ação de adicionar um novo registro
if (isset($_POST['action']) && $_POST['action'] == 'add') {
$nome = sanitize_text_field($_POST['nome']);
$token = sanitize_text_field($_POST['token']);
$status = intval($_POST['status']);
$wpdb->insert($table_name, compact('nome', 'token', 'status'));
}
// Iniciar a saída do HTML
ob_start();
?>
<div class="wrap">
<h1>Administração de Geiser</h1>
<h2>Cadastrar Leitor</h2>
<form method="post">
<input type="hidden" name="action" value="add">
<table>
<tr>
<td><label for="nome">Nome:</label></td>
<td><input type="text" name="nome" required></td>
</tr>
<tr>
<td><label for="token">Token:</label></td>
<td><input type="text" name="token" required></td>
</tr>
<tr>
<td><label for="status">Status:</label></td>
<td><input type="checkbox" name="status" value="1"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Adicionar"></td>
</tr>
</table>
</form>
</div>
<?php
// Encerrar a saída do HTML
echo ob_get_clean();
}
function geiser_dispositivos_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_leitores';
$leitores = $wpdb->get_results("SELECT * FROM $table_name", ARRAY_A);
// Iniciar a saída do HTML
ob_start();
?>
<div class="wrap">
<h1>Dispositivos Geiser</h1>
<div class="geiser-dispositivos-container" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-gap: 1em;">
<?php foreach ($leitores as $leitor): ?>
<div class="geiser-dispositivo-card" style="padding: 1em; border: 1px solid #ccc; border-radius: 4px; background-color: #f9f9f9;">
<h2 style="margin-top: 0;"><?php echo esc_html($leitor['nome']); ?></h2>
<p><strong>ID:</strong> <?php echo intval($leitor['id']); ?></p>
<p><strong>Token:</strong> <?php echo esc_html($leitor['token']); ?></p>
<p><strong>Status:</strong> <?php echo intval($leitor['status']) ? 'Ativo' : 'Inativo'; ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
// Imprimir a saída do HTML
echo ob_get_clean();
}
function geiser_logs_page() {
global $wpdb;
$table_name = $wpdb->prefix . "geiser_logs";
$logs = $wpdb->get_results("SELECT * FROM $table_name order by lastdt desc");
echo '<h1>Demonstrativo de Armazenamento na Nuvem</h1>';
// Adicionar estilos CSS
echo '<style>
table.geiser-logs-table {
width: 100%;
border-collapse: collapse;
}
table.geiser-logs-table th,
table.geiser-logs-table td {
padding: 8px;
text-align: left;
border: 1px solid #ccc;
}
table.geiser-logs-table thead {
background-color: #f5f5f5;
font-weight: bold;
}
table.geiser-logs-table tbody tr:nth-child(even) {
background-color: #f2f2f2;
}
</style>';
echo '<table class="geiser-logs-table">';
echo '<thead><tr><th>ID</th><th>Token</th><th>Data/Hora</th><th>USV/h</th><th>Temperatura</th><th>Umidade</th></tr></thead>';
echo '<tbody>';
foreach ($logs as $log) {
echo '<tr>';
echo '<td>' . $log->id . '</td>';
echo '<td>' . $log->token . '</td>';
echo '<td>' . $log->lastdt . '</td>';
echo '<td>' . $log->usvh . '</td>';
echo '<td>' . $log->temp . '</td>';
echo '<td>' . $log->hum . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
}
function geiser_analise_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_logs';
// Obtenha os dados do banco de dados
$logs = $wpdb->get_results("SELECT token, TIMESTAMP(lastdt) as datetime, AVG(usvh) as avg_usvh, AVG(temp) as avg_temp, AVG(hum) as avg_hum FROM $table_name GROUP BY token, UNIX_TIMESTAMP(lastdt) DIV (15 * 60) ORDER BY token, datetime", ARRAY_A);
// Iniciar a saída do HTML
ob_start();
?>
<div class="wrap">
<h1>Geiser Análise</h1>
<div>
<canvas id="geiserChart"></canvas>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Organiza os dados para o gráfico
var logs = <?php echo json_encode($logs); ?>;
var tokens = [];
var data = {
usvh: {},
temp: {},
hum: {}
};
logs.forEach(function (log) {
if (!data.usvh.hasOwnProperty(log.token)) {
data.usvh[log.token] = [];
data.temp[log.token] = [];
data.hum[log.token] = [];
}
data.usvh[log.token].push({x: log.datetime, y: parseFloat(log.avg_usvh)});
data.temp[log.token].push({x: log.datetime, y: parseFloat(log.avg_temp)});
data.hum[log.token].push({x: log.datetime, y: parseFloat(log.avg_hum)});
});
Object.keys(data.usvh).forEach(function (key) {
tokens.push({
label: 'Token ' + key + ' - usvh',
data: data.usvh[key],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
});
tokens.push({
label: 'Token ' + key + ' - temp',
data: data.temp[key],
fill: false,
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
});
tokens.push({
label: 'Token ' + key + ' - hum',
data: data.hum[key],
fill: false,
borderColor: 'rgb(255, 205, 86)',
tension: 0.1
});
});
// Cria o gráfico
var ctx = document.getElementById('geiserChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
datasets: tokens
},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'minute',
displayFormats: {
minute: 'HH:mm'
}
}
}
}
}
});
});
</script>
</div>
<?php
// Encerrar a saída do HTML
echo ob_get_clean();
}
// Endpoint do web service para inserir dados na tabela geiser_logs
function geiser_logs_endpoint( WP_REST_Request $request ) {
global $wpdb;
$json = $request->get_json_params();
// Insere os dados na tabela geiser_logs
$table_name = $wpdb->prefix . 'geiser_logs';
$data = array(
'id_leitor' => $json['id_leitor'],
'nome' => $json['nome'],
'ip' => $json['ip'],
'lastdt' => $json['lastdt'],
'status' => $json['status']
);
$wpdb->insert( $table_name, $data );
return 'Dados inseridos na tabela com sucesso.';
}
Informações do plugin
No fragmento abaixo, criamos o cabeçalho do plugin, onde será utilizado, para identificar o plugin.
<?php
/**
* Plugin Name: Geiser
* Plugin URI: http://maurinsoft.com.br/geiser
* Description: Gestão de Contador Geiser
* Version: 1.0
* Author: Marcelo Maurin Martins
* Author URI: https://meusite.com
*/
Registro de Componentes
O plugin possui diversos componentes, e entenda por componentes as partes que formam este, como funções e serviços.
O código abaixo, declara os componentes do plugin.
// Cria as tabelas quando o plugin for ativado
register_activation_hook( __FILE__, 'criar_tabelas' );
function criar_tabelas() {
global $wpdb;
// Cria a tabela geiser_leitores
$table_name1 = $wpdb->prefix . 'geiser_leitores';
$charset_collate = $wpdb->get_charset_collate();
$sql1 = "CREATE TABLE $table_name1 (
id mediumint(9) NOT NULL AUTO_INCREMENT,
nome varchar(50) NOT NULL,
token varchar(50) NOT NULL,
status TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql1 );
// Cria a tabela geiser_logs
$table_name2 = $wpdb->prefix . 'geiser_logs';
$sql2 = "CREATE TABLE $table_name2 (
id mediumint(9) NOT NULL AUTO_INCREMENT,
token varchar(50) NOT NULL,
lastdt datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
usvh float NOT NULL,
temp float NOT NULL,
hum float NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
dbDelta( $sql2 );
}
...
// Adiciona o menu no painel de administração
add_action( 'admin_menu', 'geiser_admin_menu' );
function geiser_admin_menu() {
add_menu_page( 'Geiser Admin', 'Geiser Admin', 'manage_options', 'geiser-admin', 'geiser_admin_page', 'geiser_admin_page' );
add_submenu_page( 'geiser-admin', 'Dispositivos', 'Dispositivos', 'manage_options', 'geiser_dispositivos_page', 'geiser_dispositivos_page' );
add_submenu_page( 'geiser-admin', 'Logs', 'Logs', 'manage_options', 'geiser_logs_page', 'geiser_logs_page' );
add_submenu_page( 'geiser-admin', 'Análise', 'Análise', 'manage_options', 'geiser_analise_page', 'geiser_analise_page' );
// Adiciona Chart.js ao painel de administração
add_action('admin_enqueue_scripts', function () {
wp_enqueue_script('chartjs', 'https://cdn.jsdelivr.net/npm/chart.js');
});
}
add_action('rest_api_init', 'registrar_rota_personalizada');
A função register_activation_hook chama uma função no momento da sua ativação. Que é feito apenas uma vez.
A função criar_tabelas , cria as tabelas no banco de dados MYSQL, que é o banco de dados do wordpress.
A função add_action permite registrar um menu no wordpress, conforme apresentamos abaixo, e a função add_menu_page, registra os itens do menu.
Cada chamada do add_menu_page, chama uma função que é referenciada, e ao clicar no menu é chamado.
add_submenu_page( 'geiser-admin', 'Análise', 'Análise', 'manage_options', 'geiser_analise_page', 'geiser_analise_page' );
function geiser_analise_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'geiser_logs';
// Obtenha os dados do banco de dados
$logs = $wpdb->get_results("SELECT token, TIMESTAMP(lastdt) as datetime, AVG(usvh) as avg_usvh, AVG(temp) as avg_temp, AVG(hum) as avg_hum FROM $table_name GROUP BY token, UNIX_TIMESTAMP(lastdt) DIV (15 * 60) ORDER BY token, datetime", ARRAY_A);
// Iniciar a saída do HTML
ob_start();
?>
<div class="wrap">
<h1>Geiser Análise</h1>
<div>
<canvas id="geiserChart"></canvas>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Organiza os dados para o gráfico
var logs = <?php echo json_encode($logs); ?>;
var tokens = [];
var data = {
usvh: {},
temp: {},
hum: {}
};
logs.forEach(function (log) {
if (!data.usvh.hasOwnProperty(log.token)) {
data.usvh[log.token] = [];
data.temp[log.token] = [];
data.hum[log.token] = [];
}
data.usvh[log.token].push({x: log.datetime, y: parseFloat(log.avg_usvh)});
data.temp[log.token].push({x: log.datetime, y: parseFloat(log.avg_temp)});
data.hum[log.token].push({x: log.datetime, y: parseFloat(log.avg_hum)});
});
Object.keys(data.usvh).forEach(function (key) {
tokens.push({
label: 'Token ' + key + ' - usvh',
data: data.usvh[key],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
});
tokens.push({
label: 'Token ' + key + ' - temp',
data: data.temp[key],
fill: false,
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
});
tokens.push({
label: 'Token ' + key + ' - hum',
data: data.hum[key],
fill: false,
borderColor: 'rgb(255, 205, 86)',
tension: 0.1
});
});
// Cria o gráfico
var ctx = document.getElementById('geiserChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
datasets: tokens
},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'minute',
displayFormats: {
minute: 'HH:mm'
}
}
}
}
}
});
});
</script>
</div>
<?php
// Encerrar a saída do HTML
echo ob_get_clean();
}
Criando um Web Service
O Web Service é um serviço web que permite através dos verbos HTTP, armazenar , visualizar e gerenciar dados.
Incluiremos um web service, através do comando .
// Adiciona a rota do web service para chamar o arquivo ./ws/registra_log.php
add_action( 'rest_api_init', 'geiser_register_api_routes' );
...
function geiser_register_api_routes() {
//...
register_rest_route( 'geiser/v1', '/registra_log', array(
'methods' => 'GET',
'callback' => 'geiser_registra_log_endpoint',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
}
) );
}
// Endpoint do web service para inserir dados na tabela geiser_logs
function geiser_logs_endpoint( WP_REST_Request $request ) {
global $wpdb;
$json = $request->get_json_params();
// Insere os dados na tabela geiser_logs
$table_name = $wpdb->prefix . 'geiser_logs';
$data = array(
'id_leitor' => $json['id_leitor'],
'nome' => $json['nome'],
'ip' => $json['ip'],
'lastdt' => $json['lastdt'],
'status' => $json['status']
);
$wpdb->insert( $table_name, $data );
return 'Dados inseridos na tabela com sucesso.';
}
Perceba que incluiremos o add_action, depois referenciamos a função geiser_register_api_routes, que registra o rest, através da chamada register_rest_route.
Que ao ser chamado, chama a função geiser_logs_endpoint.
Como chamar no arduino
O Arduino armazena o envia os dados para o web service, através do fonte: GIESER\hardware\MSTemp02
Do mesmo projeto, acima mencionado, para economizarmos tempo, irei apresentar o fragmento do código:
Utilizaremos um ethernet shield, para mostrar os dados, e para isso, incluiremos a biblioteca Ethernet.h, conforme apresentado no site.
Iremos definir a porta PORT e o endereço do URL, do wordpress, conforme apresentado no url.
E por fim chamaremos no loop a função WriteSite, que passa os dados que se deseja para o site, conforme apresentado.
#include <Ethernet.h>
#define PORT 80
#define url "http://177.26.228.0/wp-json/Geiser/v1/registro.php"
// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
// Set the static IP address to use if the DHCP fails to assign
IPAddress ip(192, 168, 0, 177);
IPAddress myDns(192, 168, 0, 1);
// Initialize the Ethernet client library
// with the IP address and port of the URL
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;
void WriteSite()
{
// give the Ethernet shield a second to initialize:
delay(1000);
Serial.print("connecting to ");
Serial.print(url);
Serial.println("...");
// if you get a connection, report back via serial:
if (client.connect(url, PORT)) {
Serial.print("connected to ");
Serial.println(client.remoteIP());
// Make a HTTP POST request:
client.println("POST /your_endpoint_here HTTP/1.1");
client.print("Host: ");
client.println(url);
client.println("Content-Type: application/json");
client.print("usvh: ");
client.println(usvh);
client.print("temp: ");
client.println(temperature);
client.print("hum: ");
client.println(humidity);
client.println("Connection: close");
// Calculate the content length
String jsonBody = String("{\"usvh\":") + String(usvh) + String(",\"temp\":") + String(temperature) + String(",\"hum\":") + String(humidity) + String("}");
client.print("Content-Length: ");
client.println(jsonBody.length());
client.println(); // Required empty line before the body
client.print(jsonBody);
client.println();
} else {
// if you didn't get a connection to the server:
Serial.println("connection failed");
}
beginMicros = micros();
}
o client é uma variavel de objeto, que armazena a conexão realizada com o servidor, nele chamaremos um cabeçalho do protocolo http, incluindo no verbo http POST as informações que desejamos incluir.
Desta forma simples conseguimos enviar dados para um servidor web.