Armazenando dados na nuvem com WORDPRESS
22 de maio de 2023O 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.