篭センサーとarduino-proxy対応版 and サーバ側はSQLite対応版
今回はSQLiteへの対応と、Proxy対応版の篭の公開です。
最近まで知らなかったのですが、PerlをインストールするとSQLiteっていう
データベースが一緒にインストールされるそうで、
今までファイルでやっていたデータの保存がSQLで出来るようになると言う。
今回の篭サーバの場合は下に書いているsetup.cgiを動かせば
DBの作成から初期データのインストールまでやっちゃいます。
例・・・・・
★DB作成
use CGI;
my $q = new CGI;
use strict;
use DBI;
# 接続
my $dbh = DBI->connect("dbi:SQLite:dbname=kago-server/kago.db");
★テーブル作成
$dbh->do("create table meibo (mac,comment, omosa, hikari);");
★初期データのインストール
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C3','本社 北側入り口付近',900, 'HI');");
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C4','本社1階南',800, 'LO');");
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C5','本社2階 階段下',700, 'HI');");
★データを読み出して確認
my $sth = $dbh->prepare("select * from meibo");
$sth->execute;
# 各データを展開表示
while (my @row = $sth->fetchrow_array) {
print "@row¥n";
}
$sth->finish;
undef $sth;
★DBから切断
$dbh->disconnect;
と超簡単。
他にもUpdateとかDeleteも簡単
テーブルの型が異なるが
★データ更新
$dbh->do("update meibo set addr='神奈川県' where id=1003;");
★データ削除
$dbh->do("delete from meibo where id=1002;");
ととても簡単。
篭サーバのプログラムはこれに対応しました。
それと篭サーバのプログラムは冗長だった関数をひとつのファイルmlib.cgiに集めて
他からはそれを呼び出しています。
SQLiteの場合、データのトランザクション処理をサポートしておりませんので
Lock処理はAP側で対応しなければなりません。
でも小規模ならばこの手軽さはすごいです。
サンプルプログラムは以下を参照願います。
■篭サーバ mlib.cgi
#! /usr/bin/perl
package mlib;
sub LockON
{
if(!open(LOCK, "> kago-server/LOCK")){
EmergencyStop(1000);
}
flock(LOCK, 2);
}
1;
sub LockOFF
{
close(LOCK);
}
1;
sub EmergencyStop
{
$line = $_[0];
$Status = $_[0];
if( $Status eq "0" ){
}
print "EmergencyStop($line)";
exit( -1 );
}
1;
sub SpaceCut
{
local($String) = @_;
$String =~ s/ //g; #// 全角空白を削除
$String =~ s/ //g; #// 半角空白を削除
$String =~ s/¥n//g; #// enterを削除
return ($String);
}
1;
■篭サーバ setup.cgi
use CGI;
my $q = new CGI;
# HTML出力
print "Content-type: text/plain¥n¥n";
use strict;
use DBI;
# 接続
my $dbh = DBI->connect("dbi:SQLite:dbname=kago-server/kago.db");
# テーブル定義
$dbh->do("create table meibo (mac,comment, omosa, hikari);");
# データ定義
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C3','仙台TNビル4階 北側入り口付近',900, 'HI');");
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C4','本社1階南',800, 'LO');");
$dbh->do("insert into meibo (mac,comment, omosa, hikari) values ('18:FE:34:EE:B2:C5','本社2階 階段下',700, 'HI');");
# テーブルの読み出し命令
my $sth = $dbh->prepare("select * from meibo");
$sth->execute;
# 各データを展開表示
while (my @row = $sth->fetchrow_array) {
print "@row¥n";
}
$sth->finish;
undef $sth;
# 切断
$dbh->disconnect;
# 完了
print "OK¥n";
■篭サーバ control.cgi
use CGI;
require 'kago-server/mlib.cgi';
use DBI;
my $q = new CGI;
$mode = $q->param('mode');
# HTML出力
print "Content-type: text/html¥n¥n";
print "<html>¥n";
print "<head>¥n";
print " <META http-equiv=Control content=no-cache>¥n";
print " <META http-equiv=Pragma content=no-cache>¥n";
print " <META http-equiv=Content-type content='text/html; charset=shift-jis'>¥n";
print " <meta http-equiv='refresh' content='60'>¥n";
print "<title>篭サーバ</title>¥n";
print "</head>¥n";
print "<body>¥n";
# 接続
my $dbh = DBI->connect("dbi:SQLite:dbname=kago-server/kago.db");
mlib::LockON();
# HTML出力
print("篭サーバ<br>");
printf("<hr>");
# テーブルの読み出し命令
my $sth = $dbh->prepare("select * from meibo");
$sth->execute;
# HTML出力
#モードが有りなら有りだけ表示
if($mode eq "ari"){
print "抽出条件:荷物有り<br>¥n";
}
else{
print "抽出条件:全部<br>¥n";
}
print "<table>";
print " <tr>";
print " <td>";
print " 荷物の有無";
print " </td>";
print " <td>";
print " 場所";
print " </td>";
print " <td>";
print " 重さ";
print " </td>";
print " <td>";
print " 赤外線";
print " </td>";
print " <td>";
print " mac";
print " </td>";
print " </tr>";
# 各データを展開表示
while (my @row = $sth->fetchrow_array) {
$mac = $row[0];
$comment = $row[1];
$omosa = $row[2];
$hikari = $row[3];
$Flag = 0;
if($hikari eq "LO"){
$Flag = 1;
}
if($omosa < 900){
$Flag = 1;
}
if($Flag eq 1){
$arinasi = "有り";
}
else{
$arinasi = "無し";
}
#モードが有りなら有りだけ表示
if($mode eq "ari"){
if($Flag eq 1){
}
else{
next;
}
}
# HTML出力
print " <tr>";
print " <td>";
#print "@row¥n";
print " $arinasi¥n";
print " </td>";
print " <td>";
print " $comment¥n";
print " </td>";
print " <td>";
print " $omosa¥n";
print " </td>";
print " <td>";
print " $hikari¥n";
print " </td>";
print " <td>";
print " $mac¥n";
print " </td>";
print " </tr>";
}
# HTML出力
print "</table>";
$sth->finish;
undef $sth;
# HTML出力
printf("<hr>");
print "抽出条件<br>";
print " <a href='/kago-server/control.cgi?mode=ari'>荷物有</a><br>";
print " <a href='/kago-server/control.cgi'>全部</a><br>";
# 切断
$dbh->disconnect;
# HTML出力
printf("<hr>");
mlib::LockOFF();
# HTML出力
print "</body>¥n";
print "</html>¥n";
exit;
■篭サーバ result.cgi
use CGI;
use DBI;
my $q = new CGI;
require 'kago-server/mlib.cgi';
# 接続
my $dbh = DBI->connect("dbi:SQLite:dbname=kago-server/kago.db");
$argc = $q->param('argc');
$mode = $q->param('mode');
$omosa = $q->param('omosa');
$hikari = $q->param('hikari');
$mac = $q->param('mac');
# HTML出力
print "Content-type: text/plain¥n¥n";
mlib::LockON();
if($mode eq "initial"){
print "PMOD=0¥n"; #ProxyMode 0 = ProxyOFF, 1 = ProxyON
print "PSRV=61.220.205.184¥n"; #ProxyServer IP Address
print "PPRT=3128¥n"; #ProxyServer PortNo
print "SSID=kurenai2¥n";
print "PASS=0092109210¥n";
print "MAC=$mac¥n";
print "SSID=kurenai3¥n";
print "PASS=0092109210¥n";
print "MAC=$mac¥n";
exit;
}
# データ更新
$dbh->do("update meibo set omosa='$omosa', hikari='$hikari' where mac='$mac';");
print "$mode¥n";
print "$omosa¥n";
print "$hikari¥n";
print "$mac¥n";
mlib::LockOFF();
exit;
■wroom-02の篭プログラム
■proxy対応版になります。
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
ESP8266WiFiMulti *pWiFiMulti;
extern "C" {
#include "user_interface.h"
}
// EEPROMの内容が信用できるか? 構造体の構造が変わった場合はこの値を変更する
#define INITIAL_STRING "170311-03"
//SSID、パスワードを書き込む構造体
struct __CONNECTINFO //接続先情報
{
char SSID[33]; //SSID
char PASS[30]; //パスワード
};
typedef struct _CONNECTINFO
{
char initial_string[10]; //EEPROMに前に書き込んであれば、initial_stringに同じものが読み込まれるので
//EEPROMの内容は信用できる。違えば信用しないでいったん初期化する。
int proxy_mode; //プロキシを使用しない場合は0 使用する場合は1以上
char proxy_server[16]; //プロキシサーバのアドレス
int proxy_port; //プロキシサーバのポート
int connect_info_count; //接続先APの数
struct __CONNECTINFO connect_info[10]; //接続先情報 最大で10組のSSIDとパスワードを記録できる
} CONNECTINFO;
CONNECTINFO connect_info;
int nMode = 0; //モード
// 0=初期化
// 1=通常
WiFiClient client; //WIFI制御
//
//エラーの場合のLED表示
//
void led_blink(int n)
{
for(int i=0;i<n;i++){
delay(500);
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
}
//
//GETする
//直接の場合
// char *host : 目的のURLのホスト部
// int port : 目的のURLのポート部
// char *path : 目的のURLのパス部分
// char *result : 結果を返すバッファー
// int size : 結果を返すバッファーのサイズ
//
// 直接の場合に client.connect での接続先は 目的のサーバとする
// GETは目的のパスを指定する し、Hostに目的のサーバを指定する。
// ex...
// 「GET /kago-server/
// Host: perl-test.azurewebsites.net」
// 説明:
// /kago-server/が目的のパス
// perl-test.azurewebsites.netが目的のサーバ
//
int http_get(char *host, int port, char *path, char *result, int size)
{
Serial.println("[HTTP] Connecting to remote server");
Serial.printf("[HTTP] host=[%s]¥n", host);
Serial.printf("[HTTP] port=[%d]¥n", port);
Serial.printf("[HTTP] path=[%s]¥n", path);
//
//サーバに接続
//
if (!client.connect(host, port)) {
Serial.println("Error contacting remote server");
return -1;
}
Serial.println("[HTTP] connected");
//
//GETする
//
char sz_path[256];
sprintf(sz_path, "GET %s HTTP/1.1¥r¥nHost: %s¥r¥nConnection: close¥r¥n¥r¥n", path, host);
client.println(sz_path);
Serial.println("Request sent");
//Serial.printf("[HTTP] path=[%s]¥n", path);
//
//値が戻るまで待つ
//
while(!client.available()){
delay(1);
}
Serial.println("[HTTP] Client available");
//
//値を読み込んでresultへコピーする
//
int nIdx = 0;
result[0]=0;
char buffer[256];
while (client.available()) {
memset(buffer, 0, sizeof(buffer));
int bytesRead = client.readBytes(buffer, sizeof(buffer)-1);
//strcat(result, buffer);
for(int i=0;i<bytesRead;i++){
if(nIdx>=(size-1-1)){
break;
}
result[nIdx]=buffer[i];
nIdx++;
}
Serial.print(bytesRead);
Serial.println(" bytes read");
delay(100);
}
result[nIdx]='¥0';
Serial.println("Response sent");
delay(100);
//Serial.print("setup-result=[");
//Serial.print(result);
//Serial.println("]");
return 0;
}
//
//GETする
//プロキシ経由の場合
// char *host : 目的のURLのホスト部
// int port : 目的のURLのポート部
// char *path : 目的のURLのパス部分
// char *proxy_host : プロキシサーバのIPアドレス
// int proxy_port : プロキシサーバのポート番号
// char *result : 結果を返すバッファー
// int size : 結果を返すバッファーのサイズ
//
// proxyを使用する場合に client.connect での接続先は プロキシサーバとする
// GETは目的のURLを指定する し、Hostにプロキシサーバを指定する。
// ex...
// 「GET http://perl-test.azurewebsites.net/kago-server/
// Host:61.220.205.184」
// 説明:
// http://perl-test.azurewebsites.net/kago-server/が目的のURL
// 61.220.205.184がプロキシサーバ
//
int http_get_proxy(char *host, int port, char *path, char *proxy_host, int proxy_port, char *result, int size)
{
Serial.println("[HTTP] Connecting to remote server");
Serial.printf("[HTTP] host=[%s]¥n", host);
Serial.printf("[HTTP] port=[%d]¥n", port);
Serial.printf("[HTTP] path=[%s]¥n", path);
Serial.printf("[HTTP] proxy_host=[%s]¥n", proxy_host);
Serial.printf("[HTTP] proxy_port=[%d]¥n", proxy_port);
//
//サーバに接続
//
if (!client.connect(proxy_host, proxy_port)) //プロキシに接続
{
Serial.println("Error contacting remote server");
return -1;
}
Serial.println("[HTTP] connected");
//
//GETする
//
char sz_path[256];
sprintf(sz_path, "GET http://%s%s HTTP/1.1¥r¥nHost: %s¥r¥n¥r¥n¥r¥n", host, path, proxy_host);
client.println(sz_path);
Serial.println("Request sent");
//Serial.printf("[HTTP] path=[%s]¥n", path);
//
//値が戻るまで待つ
//
while(!client.available()){
delay(1);
}
Serial.println("[HTTP] Client available");
//
//値を読み込んでresultへコピーする
//
int nIdx = 0;
result[0]=0;
char buffer[256];
while (client.available()) {
memset(buffer, 0, sizeof(buffer));
int bytesRead = client.readBytes(buffer, sizeof(buffer)-1);
//strcat(result, buffer);
for(int i=0;i<bytesRead;i++){
if(nIdx>=(size-1-1)){
break;
}
result[nIdx]=buffer[i];
nIdx++;
}
Serial.print(bytesRead);
Serial.println(" bytes read");
delay(100);
}
result[nIdx]='¥0';
Serial.println("Response sent");
delay(100);
//Serial.print("setup-result=[");
//Serial.print(result);
//Serial.println("]");
return 0;
}
void setup() {
//WIFIアクセスポイント制御
pWiFiMulti = new ESP8266WiFiMulti();
//シリアルの準備
Serial.begin(9600);
Serial.println("start:");
//IOの初期化
pinMode(12, INPUT);
pinMode(13, OUTPUT);
//EEPROMから接続情報を読み出す
EEPROM.begin(sizeof(CONNECTINFO));
EEPROM.get( 0, connect_info );
//
//EEPROMの中身を表示
//
connect_info.initial_string[9]='¥0'; //initial_stringを表示するが値が不定の場合に備えて10バイト目に0を入れておく
Serial.printf("connect_info initial_string=[%s]¥n", connect_info.initial_string);
if(memcmp(connect_info.initial_string, INITIAL_STRING, 9)==0){ //一度は書込み済み
Serial.printf(".proxy_mode = %d¥n", connect_info.proxy_mode); //プロキシモード
Serial.printf(".proxy_server = %s¥n", connect_info.proxy_server); //プロキシサーバ
Serial.printf(".proxy_port, %d¥n", connect_info.proxy_port); //プロキシのポート
Serial.printf(" connect_info_count=[%d]¥n", connect_info.connect_info_count); //接続情報の数
for(int i=0;i<connect_info.connect_info_count;i++){
Serial.printf(".connect_info[i].SSID=[%s]¥n", connect_info.connect_info[i].SSID); //SSID
Serial.printf(".connect_info[i].PASS=[%s]¥n", connect_info.connect_info[i].PASS); //パスワード
}
}
else{ //前に一度も書き込まれていない
//値の初期化
memset(&connect_info, 0, sizeof(connect_info));
connect_info.connect_info_count = 0;
connect_info.proxy_mode = 0;
}
// //適当に書込み
// connect_info.connect_info_count = 2;
// strcpy(connect_info.connect_info[0].SSID, "SSID1x");
// strcpy(connect_info.connect_info[0].PASS, "PASS1x");
// strcpy(connect_info.connect_info[1].SSID, "SSID2x");
// strcpy(connect_info.connect_info[1].PASS, "PASS2x");
// //EEPROMに書込み
// EEPROM.put( 0, connect_info );
// EEPROM.commit();
//
// while(1){delay(1000);}
//3秒待つ
for(uint8_t t = 3; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...¥n", t);
Serial.flush();
delay(1000);
}
//
//APを登録
//
if(memcmp(connect_info.initial_string, INITIAL_STRING, 9)==0){
for(int i=0;i<connect_info.connect_info_count;i++){
pWiFiMulti->addAP(connect_info.connect_info[i].SSID, connect_info.connect_info[i].PASS);
Serial.printf("AP: [%s][%s]¥n", connect_info.connect_info[i].SSID, connect_info.connect_info[i].PASS);
}
}
//デフォルトAP登録
pWiFiMulti->addAP("kurenai5", "0080868086");
Serial.print("setup end¥n");
}
char result[1000];
void loop() {
char szOmosa[10]; //重量センサーの値
char szHikari[10]; //フォトトランジスタの値
char szMac[33]; //MACアドレス
char host[256]; //目的のサーバー
int port; //目的のサーバーのポート
char path[256]; //目的のパス
char proxy_host[256]; //プロキシサーバ
int proxy_port; //プロキシサーバのポート
//動作確認用LED点滅
digitalWrite(13, HIGH);
delay(50);
digitalWrite(13, LOW);
delay(3000);
if((pWiFiMulti->run() == WL_CONNECTED)) { //WIFIに接続済みの場合
if(nMode==0){ //初期化モード
//
//通信の情報を表示
//
Serial.println("Connected using IP:");
Serial.println(WiFi.localIP()); //IPアドレス
Serial.println("And MAC address:");
Serial.println(WiFi.macAddress()); //MACアドレス
String sMac = WiFi.macAddress();
sprintf(szMac, "%s", sMac.c_str());
//
//目的サーバを情報を設定
//
// char url[256];
// sprintf(url, "http://perl-test.azurewebsites.net/kago-server/result.cgi?mode=initial&&mac=%s", szMac);
sprintf(host, "perl-test.azurewebsites.net");
port = 80;
sprintf(path, "/kago-server/result.cgi?mode=initial&&mac=%s", szMac);
memset(result, 0, sizeof(result));
// int ret = http_get(url, result, sizeof(result));
int ret;
if(connect_info.proxy_mode){ //プロキシモードなら
//プロキシサーバの情報を設定
strcpy(proxy_host, connect_info.proxy_server);
proxy_port = connect_info.proxy_port;
//GET
ret = http_get_proxy(host, port, path, proxy_host, proxy_port, result, sizeof(result));
}
else{
//GET
ret = http_get(host, port, path, result, sizeof(result));
}
if(ret >= 0) {
//結果表示
Serial.printf("result = [%s]¥n", result);
//
//結果を解析して接続情報を編集する
//
int nIdxStart = 0;
int len = strlen(result);
int nInfoCount = 0;
char SSID[33];
char PASS[30];
for(int i=0;i<len;i++){
if(result[i]==10){ //改行
char work[33];
result[i]='¥0';//'¥0';
strcpy(work, &result[nIdxStart]);
if(memcmp(work, "PMOD=", 5)==0){
connect_info.proxy_mode=atoi(&work[5]);
}
if(memcmp(work, "PSRV=", 5)==0){
strcpy(connect_info.proxy_server, &work[5]);
}
if(memcmp(work, "PPRT=", 5)==0){
connect_info.proxy_port=atoi(&work[5]);
}
if(memcmp(work, "SSID=", 5)==0){
strcpy(SSID, &work[5]);
}
if(memcmp(work, "PASS=", 5)==0){
strcpy(PASS, &work[5]);
}
if(memcmp(work, "MAC=", 4)==0){
strcpy(connect_info.connect_info[nInfoCount].SSID, SSID);
strcpy(connect_info.connect_info[nInfoCount].PASS, PASS);
nInfoCount++;
connect_info.connect_info_count = nInfoCount;
strcpy(connect_info.initial_string, INITIAL_STRING);
}
nIdxStart=i+1;
}
}
//接続情報を表示
Serial.printf("connect_info connect_info_count=[%d]¥n", connect_info.connect_info_count);
connect_info.initial_string[9]='¥0';
Serial.printf(" initial_string=[%s]¥n", connect_info.initial_string);
Serial.printf(".proxy_mode = %d¥n", connect_info.proxy_mode);
Serial.printf(".proxy_server = %s¥n", connect_info.proxy_server);
Serial.printf(".proxy_port, %d¥n", connect_info.proxy_port);
for(int i=0;i<connect_info.connect_info_count;i++){
Serial.printf(".connect_info[i].SSID=[%s]¥n", connect_info.connect_info[i].SSID);
Serial.printf(".connect_info[i].PASS=[%s]¥n", connect_info.connect_info[i].PASS);
}
//接続情報をEEPROMへ書き出し
EEPROM.put( 0, connect_info );
EEPROM.commit();
//WEBから持ってきた情報でAPを設定
if(connect_info.connect_info_count){
Serial.printf("APClean¥n");
delete pWiFiMulti;
pWiFiMulti = new ESP8266WiFiMulti();
for(int i=0;i<connect_info.connect_info_count;i++){
Serial.printf("AP: [%s][%s]¥n", connect_info.connect_info[i].SSID, connect_info.connect_info[i].PASS);
pWiFiMulti->addAP(connect_info.connect_info[i].SSID, connect_info.connect_info[i].PASS);
}
}
//デフォルトAP登録
pWiFiMulti->addAP("kurenai5", "0080868086");
nMode = 1; //通常モードへ
}
else{ //GETがエラー
//エラー
led_blink( 2 ); //LED点滅 2回
}
}else
if(nMode==1){ //通常モード
//
//TOUTからアナログ値を入力 圧力センサー値
//
char str[256];
int val = system_adc_read();
//値を表示
sprintf(str, "val = %d¥n", val);
Serial.println(str);
sprintf(szOmosa, "%d", val);
//
//デジタルの12番からフォトトランジスター値を入力 赤外線の明るさ
//
val = digitalRead(12);
if( val == HIGH ){ //明るい
Serial.println("H");
sprintf(szHikari, "HI");
}else{ //暗い
Serial.println("L");
sprintf(szHikari, "LO");
}
//MACアドレスを取得
String sMac = WiFi.macAddress();
sprintf(szMac, "%s", sMac.c_str());
//
//目的サーバの情報を設定
//
//char url[256];
//sprintf(url, "http://perl-test.azurewebsites.net/kago-server/result.cgi?omosa=%s&hikari=%s&mac=%s", szOmosa, szHikari, szMac);
sprintf(host, "perl-test.azurewebsites.net");
port = 80;
sprintf(path, "/kago-server/result.cgi?omosa=%s&hikari=%s&mac=%s", szOmosa, szHikari, szMac);
memset(result, 0, sizeof(result));
// int ret = http_get(url, result);
// int ret = http_get(host, port, path, result, sizeof(result));
int ret;
if(connect_info.proxy_mode){ //プロキシモードなら
//プロキシサーバの情報を設定
strcpy(proxy_host, connect_info.proxy_server);
proxy_port = connect_info.proxy_port;
//GET
ret = http_get_proxy(host, port, path, proxy_host, proxy_port, result, sizeof(result));
}
else{
//GET
ret = http_get(host, port, path, result, sizeof(result));
}
if(ret >= 0) {
Serial.printf("result = [%s]¥n", result);
}
else{ //GETがエラー
led_blink( 2 ); //LED点滅 2回
}
}
}
else{ //WIFIに接続されていない
led_blink( 1 ); //LED点滅 1回
}
}