Hobby Science&Experiment

愛と工作の日々

趣味でやっている工作や勉強したことのメモ書きです。

Cool Arduino MIDI Projects

ArduinoMIDIのかっこいいプロジェクトを集めてみました。
LOOK MUM NO COMPUTERの工作は圧倒的ですね。工作過程も出してくれているので大変勉強になります。

www.midi.org

www.youtube.com

homemadegarbage.com

www.youtube.com

www.youtube.com
https://www.lookmumnocomputer.com/projects/#/big-button

Arduino UnoのMIDI LibraryでDAWのドラムパターンを鳴らす

Arduino MIDI Libraryを使ってDAWのドラムやシンセを鳴らす試みです。これが出来ればArduinoベースのMIDIコントローラーや電子ドラム、シンセなどが作れるようになるはずです。Arduino MIDI controller等で検索するとヒットするこのかっこいいプロジェクトのコード・回路は試したところ動作したのですが、ポットから信号が出続けたりして制御が難しかったのと、コードが長くモディファイしにくかったため別途書き直すことにしました。

準備

・Arudino Uno (USB MIDIバイス化済み→こちらもしくはこちら参考)
・何かしらのDAW
 (本記事ではWaveform FreeもしくはWeb audio synthesizerで動作確認しました。)

コードについて

コードは主に下記の2記事を参考にしました。
Arduino MIDI Library の使い方 - Qiita
超簡単!Arduino UNOをMIDIコントローラーにしよう! <Arduino MIDI Library 4.2対応版> - Qiita
MIDI.sendNoteOn(pitch, velocity, channel)というコマンドでドラム(もしくはシンセ)を鳴らします。 pitchの部分が0~127までの数字で鍵盤の最低音~最高音に対応しているようです。私の使用したDAWでは鍵盤のC#3(pitch=49)がクラッシュシンバル、C2がKICKというように対応していますので、クラッシュ→キック→ハンドクラップ→スネアの順に鳴らすようにしました。

#include <MIDI.h> //MIDIライブラリ使用のためのヘッダファイル読み込み
MIDI_CREATE_DEFAULT_INSTANCE(); // MIDIクラスのインスタンスとして"MIDI"を生成する。
#define CRASH 49 //C#3
#define KICK 36 //C2
#define CLAP 39 //D#2
#define SNARE 38 //D2
int DRUM[] = {CRASH, KICK, CLAP, SNARE};
int NUM_DRUM = sizeof(DRUM)/sizeof(DRUM[0]);
int i=0;

void setup()
{   
    Serial.begin(115200);
    MIDI.begin(4); // MIDIインスタンスの初期化、その際チャンネル4のみをlisten
}

void loop(){
  for (i = 0; i < NUM_DRUM; i++){
    MIDI.sendNoteOn(DRUM[i],127,1);  // ノートオン(pitch, velo, channel) 
    delay(500);    
    MIDI.sendNoteOff(DRUM[i],0,1);   // ノートオフ
    delay(500);          
    }
}

動作


プログラム通りの順にドラム音が出ました。ここまで出来たら原理的には何でも作れそうな感じがしちゃいますね。
パターン化することでドラムマシンや電子ドラムが作れます。プラグインをシンセにすればシンセがそのまま鳴らせるので自動演奏器のようにしても面白いかもしれません。

今後

ドラムマシン、電子ドラム、自動演奏器、MIDIコントローラー的な奴を少しずつ進めていきたいと思います。

ほこりセンサー+M5StickCで室内ダスト濃度をモニタリング【Ambient】

前回の記事ではSHARPほこりセンサーGP2Y1010AUをArduino Unoで動作させました。実際の運用では長時間に渡る連続測定ログと、その瞬間の値をディスプレイ等で確認できると便利です。それらの目的を達するにはM5StickCが便利かと思い、試してみることにしました。
www.switch-science.com

配線

Arduino Unoの場合の配線と基本的に同じですが、LED駆動ピンと信号測定ピンはそれぞれ26pinと36pinに配線しました。こちらの記事によると26pinはdigitalWrite()に対応し、36pinはanalogRead()に対応しているようだったためです。

コード(M5Stickによる測定、表示まで)

基本的に前回も使用したsharpsensoruser氏のコードにM5StickC使用のコードを一部追加しただけになっています。

// Choose program options.
//#define PRINT_RAW_DATA
#define USE_AVG
#include <M5StickC.h>

// Arduino pin numbers.
const int sharpLEDPin = 26;   // Arduino digital pin 7 connect to sensor LED.
const int sharpVoPin = 36;   // Arduino analog pin 5 connect to sensor Vo.

// For averaging last N raw voltage readings.
#ifdef USE_AVG
#define N 100
static unsigned long VoRawTotal = 0;
static int VoRawCount = 0;
#endif // USE_AVG

// Set the typical output voltage in Volts when there is zero dust. 
static float Voc = 0.6;

// Use the typical sensitivity in units of V per 100ug/m3.
const float K = 0.5;
  
/////////////////////////////////////////////////////////////////////////////

// Helper functions to print a data value to the serial monitor.
void printValue(String text, unsigned int value, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  if (!isLast) {
    Serial.print(", ");
  }
}
void printFValue(String text, float value, String units, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  Serial.print(units);
  if (!isLast) {
    Serial.print(", ");
  }
}

/////////////////////////////////////////////////////////////////////////////

// Arduino setup function.
void setup() {
  // Set LED pin for output.
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);  
  pinMode(sharpLEDPin, OUTPUT);
  
  // Start the hardware serial port for the serial monitor.
  Serial.begin(9600);
  
  // Wait two seconds for startup.
  delay(2000);
  Serial.println("");
  Serial.println("GP2Y1014AU0F Demo");
  Serial.println("=================");
}

// Arduino main loop.
void loop() {  
  // Turn on the dust sensor LED by setting digital pin LOW.
  digitalWrite(sharpLEDPin, LOW);

  // Wait 0.28ms before taking a reading of the output voltage as per spec.
  delayMicroseconds(280);

  // Record the output voltage. This operation takes around 100 microseconds.
  int VoRaw = analogRead(sharpVoPin);
  
  // Turn the dust sensor LED off by setting digital pin HIGH.
  digitalWrite(sharpLEDPin, HIGH);

  // Wait for remainder of the 10ms cycle = 10000 - 280 - 100 microseconds.
  delayMicroseconds(9620);
  
  // Print raw voltage value (number from 0 to 1023).
  #ifdef PRINT_RAW_DATA
  printValue("VoRaw", VoRaw, true);
  Serial.println("");
  #endif // PRINT_RAW_DATA
  
  // Use averaging if needed.
  float Vo = VoRaw;
  #ifdef USE_AVG
  VoRawTotal += VoRaw;
  VoRawCount++;
  if ( VoRawCount >= N ) {
    Vo = 1.0 * VoRawTotal / N;
    VoRawCount = 0;
    VoRawTotal = 0;
  } else {
    return;
  }
  #endif // USE_AVG

  // Compute the output voltage in Volts.
  Vo = Vo / 1024.0 * 5.0;
  printFValue("Vo", Vo*1000.0, "mV");

  // Convert to Dust Density in units of ug/m3.
  float dV = Vo - Voc;
  if ( dV < 0 ) {
    dV = 0;
    Voc = Vo;
  }
  float dustDensity = dV / K * 100.0;
  printFValue("DustDensity", dustDensity, "ug/m3", true);
  Serial.println("");

  M5.Lcd.setCursor(0, 50);
  M5.Lcd.printf("%6.2f mg/m3", dustDensity);
  M5.Lcd.setCursor(0, 10);
  M5.Lcd.printf("Dust Density"); 
  
} // END PROGRAM

無事ディスプレイに最新のダスト密度が表示されました。
f:id:tara-chang:20210224220704p:plain

コード2(M5Stickによる測定、ディスプレイ表示、Wifi設定、Ambient送信まで)

先ほどのコードにWifi設定、Ambient送信までを追加しました。Ambientは過去の記事でも扱いましたが、非常に簡便に扱えるクラウドIoTサービスです。使用の際はサインインしチャネルIDとライトキーを控えておく必要があります。またAmbientサーバーへの送信頻度はあまり高くても仕方ないので、60回の測定に一回送信するようになっています。

#define USE_AVG
#include <M5StickC.h>
include "Ambient.h"

WiFiClient client;
Ambient ambient;

const char* ssid = "*****************"; //"自分の使うWifiのSSIDをここに書く"
const char* password = "*******************"; //"上記のパスワードを書く"

unsigned int channelId = 123456; // 自分のAmbientのチャネルIDに置き換える
const char* writeKey = "********************"; // 自分のAmbientのライトキーに置き換える

// Arduino pin numbers.
const int sharpLEDPin = 26;   // Arduino digital pin 7 connect to sensor LED.
const int sharpVoPin = 36;   // Arduino analog pin 5 connect to sensor Vo.

// For averaging last N raw voltage readings.
#ifdef USE_AVG
#define N 100
static unsigned long VoRawTotal = 0;
static int VoRawCount = 0;
#endif // USE_AVG
int count = 0;
// Set the typical output voltage in Volts when there is zero dust. 
static float Voc = 0.6;

// Use the typical sensitivity in units of V per 100ug/m3.
const float K = 0.5;
  
/////////////////////////////////////////////////////////////////////////////

// Helper functions to print a data value to the serial monitor.
void printValue(String text, unsigned int value, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  if (!isLast) {
    Serial.print(", ");
  }
}
void printFValue(String text, float value, String units, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  Serial.print(units);
  if (!isLast) {
    Serial.print(", ");
  }
}

/////////////////////////////////////////////////////////////////////////////

// Arduino setup function.
void setup() {
  // Set LED pin for output.
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);  
  pinMode(sharpLEDPin, OUTPUT);
  
  // Start the hardware serial port for the serial monitor.
  Serial.begin(9600);
  
  // Wait two seconds for startup.
  delay(2000);
  Serial.println("");
  Serial.println("GP2Y1014AU0F Demo");
  Serial.println("=================");
}

// Arduino main loop.
void loop() {  
  // Turn on the dust sensor LED by setting digital pin LOW.
  digitalWrite(sharpLEDPin, LOW);

  // Wait 0.28ms before taking a reading of the output voltage as per spec.
  delayMicroseconds(280);

  // Record the output voltage. This operation takes around 100 microseconds.
  int VoRaw = analogRead(sharpVoPin);
  
  // Turn the dust sensor LED off by setting digital pin HIGH.
  digitalWrite(sharpLEDPin, HIGH);

  // Wait for remainder of the 10ms cycle = 10000 - 280 - 100 microseconds.
  delayMicroseconds(9620);
  
  // Print raw voltage value (number from 0 to 1023).
  #ifdef PRINT_RAW_DATA
  printValue("VoRaw", VoRaw, true);
  Serial.println("");
  #endif // PRINT_RAW_DATA
  
  // Use averaging if needed.
  float Vo = VoRaw;
  #ifdef USE_AVG
  VoRawTotal += VoRaw;
  VoRawCount++;
  if ( VoRawCount >= N ) {
    Vo = 1.0 * VoRawTotal / N;
    VoRawCount = 0;
    VoRawTotal = 0;
  } else {
    return;
  }
  #endif // USE_AVG

  // Compute the output voltage in Volts.
  Vo = Vo / 1024.0 * 5.0;
  printFValue("Vo", Vo*1000.0, "mV");

  // Convert to Dust Density in units of ug/m3.
  float dV = Vo - Voc;
  if ( dV < 0 ) {
    dV = 0;
    Voc = Vo;
  }
  float dustDensity = dV / K * 100.0;
  printFValue("DustDensity", dustDensity, "ug/m3", true);
  Serial.println("");

  M5.Lcd.setCursor(0, 50);
  M5.Lcd.printf("%6.2f mg/m3", dustDensity);
  M5.Lcd.setCursor(0, 10);
  M5.Lcd.printf("Dust Density"); 
  
//ambient
  count++;
  if (count = 60){
  count = 0;
  WiFi.begin(ssid, password);  
  while (WiFi.status() != WL_CONNECTED) {  
    delay(500);
    Serial.print(".");
  }
  Serial.print("WiFi connected\r\nIP address: ");
  Serial.println(WiFi.localIP());

  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化

  ambient.set(1,dustDensity);
  ambient.send();            
  }//  Ambientにデーターを送信
} // END PROGRAM

動作確認

Ambientへも送信されていることが確認出来ました。
f:id:tara-chang:20210224220748p:plain
しかし問題として、前回Arduinoで測定したダスト密度より一桁高くなってしまっていることや、S/Nが悪くなっています。プログラムに修正が必要なものと思われます。

SHARPほこりセンサーGP2Y1010AUをArduinoで動作させる

室内のほこり濃度を可視化して一定値を超えたら換気したりルンバで掃除したりするというスマートハウスの一機能を構想しています。まず第一歩としてホコリセンサーを触ってみることにしました。どの程度の感度があるのか、目的のような使い方が出来るのかを知りたいです。今回はArduinoによる動作確認と、ほこりへの応答を確認するところまでとなります。

ほこりセンサーの調達

SHARPのほこりセンサーGP2Y1010AUを使用しました。大きな貫通穴が開いた金属筐体が特徴で、大きさはマッチ箱くらいです。空気清浄機なんかにも使用されているようです。秋月電子で900円でした。Alieepressでも同様の製品が安価で入手できそうです。

f:id:tara-chang:20210223230025p:plain
届いたほこりセンサー。裏面はなんか汚らしい感じです!笑

購入後に気づいたのですが、精度が向上したGP2Y1014AU0Fモデルやデジタル出力が可能なタイプもあるようです。

センサー原理

データシートもしくはsharpsensoruserにてに詳しい説明があります。貫通穴を通って流入する空気に対してLEDより光が照射され、ほこりによって散乱された光を検出器で検出し、信号を増幅して出力するようです。ほこり濃度によってどの程度の信号電圧が得られるかはデータシートに記載されています。
f:id:tara-chang:20210223223335p:plain
ほこり濃度0~0.5mg/m3程度まではほぼリニアに増加し、0.5以降はサチって来るようです。また信号はパルスで出力されるようで、タイミングを逃さないような回路及びプログラムを組む必要があるみたいです。

配線

データシート及びsharpsensoruserに配線図があります。コンデンサと抵抗はLEDによるパルス駆動のために必須であり、定数も合わせる必要があるとのことです。

プログラム

基本的にsharpsensoruserのコードで動作しました。sharpLEDPinは測定光源のLED
LED駆動用のピンに、sharpVoPinはアウトプット電圧のRaed用のピンにアサインします。

/////////////////////////////////////////////////////////////////////////////
// Sharp GP2Y1014AU0F Dust Sensor Demo
//
// Board Connection:
//   GP2Y1014    Arduino
//   V-LED       Between R1 and C1
//   LED-GND     C1 and GND
//   LED         Pin 7
//   S-GND       GND
//   Vo          A5
//   Vcc         5V
//
// Serial monitor setting:
//   9600 baud
/////////////////////////////////////////////////////////////////////////////

// Choose program options.
//#define PRINT_RAW_DATA
#define USE_AVG

// Arduino pin numbers.
const int sharpLEDPin = 2;   // Arduino digital pin 7 connect to sensor LED.
const int sharpVoPin = A0;   // Arduino analog pin 5 connect to sensor Vo.

// For averaging last N raw voltage readings.
#ifdef USE_AVG
#define N 100
static unsigned long VoRawTotal = 0;
static int VoRawCount = 0;
#endif // USE_AVG

// Set the typical output voltage in Volts when there is zero dust. 
static float Voc = 0.6;

// Use the typical sensitivity in units of V per 100ug/m3.
const float K = 0.5;
  
/////////////////////////////////////////////////////////////////////////////

// Helper functions to print a data value to the serial monitor.
void printValue(String text, unsigned int value, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  if (!isLast) {
    Serial.print(", ");
  }
}
void printFValue(String text, float value, String units, bool isLast = false) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(value);
  Serial.print(units);
  if (!isLast) {
    Serial.print(", ");
  }
}

/////////////////////////////////////////////////////////////////////////////

// Arduino setup function.
void setup() {
  // Set LED pin for output.
  pinMode(sharpLEDPin, OUTPUT);
  
  // Start the hardware serial port for the serial monitor.
  Serial.begin(9600);
  
  // Wait two seconds for startup.
  delay(2000);
  Serial.println("");
  Serial.println("GP2Y1014AU0F Demo");
  Serial.println("=================");
}

// Arduino main loop.
void loop() {  
  // Turn on the dust sensor LED by setting digital pin LOW.
  digitalWrite(sharpLEDPin, LOW);

  // Wait 0.28ms before taking a reading of the output voltage as per spec.
  delayMicroseconds(280);

  // Record the output voltage. This operation takes around 100 microseconds.
  int VoRaw = analogRead(sharpVoPin);
  
  // Turn the dust sensor LED off by setting digital pin HIGH.
  digitalWrite(sharpLEDPin, HIGH);

  // Wait for remainder of the 10ms cycle = 10000 - 280 - 100 microseconds.
  delayMicroseconds(9620);
  
  // Print raw voltage value (number from 0 to 1023).
  #ifdef PRINT_RAW_DATA
  printValue("VoRaw", VoRaw, true);
  Serial.println("");
  #endif // PRINT_RAW_DATA
  
  // Use averaging if needed.
  float Vo = VoRaw;
  #ifdef USE_AVG
  VoRawTotal += VoRaw;
  VoRawCount++;
  if ( VoRawCount >= N ) {
    Vo = 1.0 * VoRawTotal / N;
    VoRawCount = 0;
    VoRawTotal = 0;
  } else {
    return;
  }
  #endif // USE_AVG

  // Compute the output voltage in Volts.
  Vo = Vo / 1024.0 * 5.0;
  printFValue("Vo", Vo*1000.0, "mV");

  // Convert to Dust Density in units of ug/m3.
  float dV = Vo - Voc;
  if ( dV < 0 ) {
    dV = 0;
    Voc = Vo;
  }
  float dustDensity = dV / K * 100.0;
  printFValue("DustDensity", dustDensity, "ug/m3", true);
  Serial.println("");
  
} // END PROGRAM

動作確認

ホコリ密度が出力されました。試しにセンサー付近でほこりをかぶったクリーナーをパタパタさせるとパルス状の信号の増加が見られました。結構敏感に動作しているようです。


f:id:tara-chang:20210226071113p:plain

今後やること

・回路、プログラムの観点から測定精度を高める。
・ESP32などで測定を行い、サーバー上でデータを蓄積する。
・ホコリセンサーで可視化出来ること、出来ないことをはっきりさせる。

PythonでWeb漫画の更新をLine通知する

ONEさんのWeb漫画「ワンパンマン」が好きなのですが、更新されたことに気付くのにいつも遅れてしまいます。「1日一回自動で更新をチェックし通知するプログラム」を作ってみたいと思います。ついでにWebスクレイピングの勉強もしてしまおうという魂胆です。

ライブラリインポート

BeautifulSoup4なるライブラリが必要です。何なんでしょうこの名前…。

sudo pip install BeautifulSoup4

ページ上のHTML情報を取得する。

import requests
from bs4 import BeautifulSoup
# Webページを取得して解析する

load_url = "http://galaxyheavyblow.web.fc2.com/"
html = requests.get(load_url)
soup = BeautifulSoup(html.content, "html.parser")

# HTML全体を表示する
print(soup)

結果

<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-type"/>
<meta content="text/javascript" http-equiv="Content-Script-Type"/></head></html>
<title>ワンパンマン</title>

<body link="#000000" style="background:#D2B48C" vlink="#000000">
<font color="#000000">
<br/>
<center>
<h1>ワンパンマン</h1>
<br/>
<div id="bgi"><img alt="" height="848" src="/top.jpg" width="600"/></div>
<br/>
<br/>
<a href="http://tonarinoyj.jp/" target="_blank"><img alt="" border="0" height="162" src="/tonarino.jpg" width="600"/> </a><br/>
<p>『となりのヤングジャンプ』で連載中。(2012614日〜)</p>
<br/><iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/p7fWKezF4iU" width="560"></iframe>
<br/><br/>
<a href="http://urasunday.com/" target="_blank"><img alt="" border="0" height="162" src="/urasunday.jpg" width="600"/> </a><br/>
<p>『裏サンデー』で連載中。(2012418日〜)</p>
<br/><iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/_E0wbdZZKRc" width="560"></iframe><br/>
<br/>
<a href="touhyou2016.html">【原作】第3回キャラクター人気投票結果発表【ワンパンマン】</a><br/><br/>
<p>※画像が表示されないときは、再読み込みするか、表示されない画像を右クリックして『画像の表示』を選ぶと表示されます。画像が中途半端に表示されたときは、時間をおいて再読み込みするといいみたいです。</p>
<a href="/fc2-imageviewer/?aid=1&amp;iid=2">第1話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=3">第2話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=4">第3話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=5">第4話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=6">第5話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=7">第6話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=8">第7話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=9">第8話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=10">第9話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=11">第10話</a><br/>
・
・
・
=1&amp;iid=122">第121話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=123">第122話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=124">第123話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=125">第124話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=126">第125話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=127">第126話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=128">第127話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=129">第128話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=131">第129話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=132">第130話</a><br/>
<a href="/fc2-imageviewer/?aid=1&amp;iid=133">第131話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=134">第132話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=135">第133話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=136">第134話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=137">第135話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=138">第136話</a> <a href="/fc2-imageviewer/?aid=1&amp;iid=139">第137話</a><br/>
          2021年1月22日 137話更新 <br/>
<a href="http://twitter.com/ONE_rakugaki">twitter</a> <a href="http://form1.fc2.com/form/?id=498522">mail</a> <br/>
<br/>
<br/>
<script language="JavaScript" src="http://counter1.fc2.com/counter.php?id=4851767" type="text/javascript"></script><noscript><img src="http://counter1.fc2.com/counter_img.php?id=4851767"/></noscript>
</center>
<br/>
</font>
<script type="text/javascript"><!--
var fc2footerparam = 'charset=' + (document.charset ? document.charset : document.characterSet) + '&url=' + document.location + '&service=0&r=' + Math.floor(Math.random()*99999999999);
var fc2footertag = '<' + 'script src="//vip.chps-api.fc2.com/apis/footer/?' + fc2footerparam + '" charset="UTF-8"><' + '/script>';
document.write(fc2footertag);
//--></script>
<!-- FC2, inc.-->
<img alt="inserted by FC2 system" height="0" src="//media.fc2.com/counter_img.php?id=50" style="visibility:hidden" width="0"/>
<!-- FC2, inc.-->
</body>

HTMLが丸っと抽出されました。もちろんこのままでは何のこっちゃですのでここから特定のタグのみを抽出します。

タグで抽出する

HTML情報の中から特定のタグのみを抽出することが出来ます。第〇話という文字列はいずれも"a"というタグが付いていますので、これで抽出してみようと思います。

import requests
from bs4 import BeautifulSoup
# Webページを取得して解析する

load_url = "http://galaxyheavyblow.web.fc2.com/"
html = requests.get(load_url)
soup = BeautifulSoup(html.content, "html.parser")

# HTML全体を表示する
print(soup.find_all("a"))

結果

[<a href="http://tonarinoyj.jp/" target="_blank"><img alt="" border="0" height="162" src="/tonarino.jpg" width="600"/> </a>, <a href="http://urasunday.com/" target="_blank"><img alt="" border="0" height="162" src="/urasunday.jpg" width="600"/> </a>, <a href="touhyou2016.html">【原作】第3回キャラクター人気投票結果発表【ワンパンマン】</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=2">第1話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=3">第2話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=4">第3話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=5">第4話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=6">第5話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=7">第6話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=8">第7話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=9">第8話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=10">第9話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=11">第10話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=12">第11話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=13">第12話
・
・
aid=1&amp;iid=134">第132話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=135">第133話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=136">第134話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=137">第135話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=138">第136話</a>, <a href="/fc2-imageviewer/?aid=1&amp;iid=139">第137話</a>, <a href="http://twitter.com/ONE_rakugaki">twitter</a>, <a href="http://form1.fc2.com/form/?id=498522">mail</a>]

第〇話という部分がかなり抽出できましたが、まだごちゃごちゃしています。ここからは通常のリストの処理になります。

文字列を抽出する

先ほど抽出したリストの各要素をfor文で取り出し、かつその要素の文字列部分を".text"により取り出しています。また"第" と "話"を含む要素のみを取り出すことでページ上の話数を得ます。

import requests
from bs4 import BeautifulSoup
# Webページを取得して解析する

load_url = "http://galaxyheavyblow.web.fc2.com/"
html = requests.get(load_url)
soup = BeautifulSoup(html.content, "html.parser")

for element in soup.find_all("a"):    # すべてのaタグを検索して表示
    if "第" and "話" in element.text:
        print(element.text)

結果

第1話
第2話
第3話
第4話
・
・
第135話
第136話
第137話

必要な情報は抽出できました。あとは通知プログラムとして完成させるだけです。

完成版のプログラム

話数を取得し情報をログ("onepunch_log.txt")として保存します。前回実行時のログと今回の話数を比較し、異なればLINEで通知するようになっています。LINE送信のコードについてはこちらの記事にまとめました。

import requests
from bs4 import BeautifulSoup
import os

def get_latest_ep():#話数を取得
    load_url = "http://galaxyheavyblow.web.fc2.com/"
    html = requests.get(load_url)
    soup = BeautifulSoup(html.content, "html.parser")#全HTML全体
    for element in soup.find_all("a"):    # すべてのaタグを検索
        if "第" and "話" in element.text: # 要素を文字列化し、話数を抽出
            latest_ep = element.text
    return latest_ep

def log_check(content):#最新話かどうか判定
    logfile_name = "onepunch_log.txt"
    if not os.path.exists("./"+logfile_name):#ログファイルの存在を確認
        file = open(logfile_name, 'w')#なければ作る
    file = open(logfile_name, 'r')#読み取りでファイルを開く
    if file.readline() == content:
        checker = False
    else:
        checker = True
        file = open(logfile_name, 'w')
        file.write(content)
        file.close()    
    return checker

def send_line(text):#ライン送信
    url ='https://notify-api.line.me/api/notify'
    token = "トークンを張り付け"
    headers ={'Authorization' : 'Bearer ' + token}
    message = "ワンパンマン(ONE版)"+text+"が更新されました"
    payload = {'message' : message}
    p = requests.post(url, headers=headers, data=payload)
    print(p)

if __name__ == "__main__":
    latest_episode = get_latest_ep()
    if log_check(latest_episode):
        send_line(latest_episode)

定期実行

ラズパイのcrontabで定期実行を行います。頻繁にアクセスすると迷惑となりますので、1日に一回、12:20分に実行することにしました。

20 12 * * * sudo python3 /home/pi/210205_one_punch_man.py

動作

こんな感じで通知が来ます。実際に更新通知はまだ来てないですが、来る日を楽しみにしています!
f:id:tara-chang:20210214195736p:plain
※追記 動作確認出来ました。

Arudino Nano + ミニUSBホストシールドを試す

前回の記事ではArduino UnoとUSBホストシールドを使用しましたが、デバイス小型化のためにArduino Nanoを用いることが出来ないかと考えました。探してみるとArduino Nanoで使えそうな「ミニUSBホストシールド(Mini USB Host Shield)」なるものが見つかりました。
GAOHOU ミニUSBホストシールド2.0 ADK SLR開発ツール互換SPIインターフェイス
ただよく読むと、こちらArduino NanoではなくArduino Pro microやAruduino Pro Miniというボードに対応してるみたいです。
HiLetgo ATMEGA328P 3.3V/8M Arduino PROミニ 互換性のある開発ボード改良版 (3.3V) (2個セット)
それでもArduino Nanoが使い慣れてるので、これを使いたい!というわけで接続方法を調べながら実証しましたので記録しておきます。こちらのサイトを参考にさせて頂きました。
ht-deko.com

配線

以下の表のような配線で無事通信を行うことが出来ました。パターンカットなしでは動作せず、必要であることも確認出来ました。

Arduino Nano側 USB host shield mini側
GND
GND
5V
VSUB
3V3
3V3
RST
RST
D9
INT
D10
SS
D11
MOSI
D12
MISO
D13
SCK

動作確認

不具合

実験の途中一台Arduino Nanoが壊れました。原因は良く分かってないです。安価なNanoとは言え初めてマイコン壊したのでショックでした…。

教訓

ミニUSBホストシールドはArduino Nanoと接続するにはピン配置が違いすぎます。おとなしくPro miniを使うかPro Microを使うべきでしょう。しかしこれらのボードはNanoよりもGPIOピンの数が少ないのでケースバイケースとも言えます。

ラズパイでCO2/気圧/温度/湿度/ガスの総合測定+モニタリング【Ambient】

以前の記事で複合センサーBME680とCO2センサーMH-Z19BをRaspberry Piで使用するところまではやりました。
jakejake.hatenablog.com
jakejake.hatenablog.com
今回はそれらのセンサーの取得値(CO2/気圧/温度/湿度/有機ガス)をまとめて測定し、スマホから好きな時に確認出来るようにしたいと思います。

配線

f:id:tara-chang:20210131190954p:plain

ラズパイ側 BME680 MH-Z19B
3V3
CN1(Vin)
--
SCL1(3pin)
CN2(SCL)
--
SDA1(2pin)
CN3(SDA)
--
GND
CN4(GND)
GND(黒線)
5V0
--
VCC(赤線)
TXD0(14pin)
--
RX(青線)
RXD0(15pin)
--
TX(黄線)

※pinはGPIO番号です。

モニタリング

モニタリングにはAmbientを使わせて頂きました。一見難しそうですが利用は非常にシンプルで簡単でした。
ambidata.io
サインイン後、pythonライブラリーをインストールし、コードを数行付け足すだけで使用することが出来ます。
ambidata.io

コード全体

#bme680_and_CO2.py
#!/usr/bin/env python

import bme680
import time

print("""read-all.py - Displays temperature, pressure, humidity, and gas.

Press Ctrl+C to exit!

""")

try:
    sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
    sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)

# These calibration data can safely be commented
# out, if desired.

print('Calibration data:')
for name in dir(sensor.calibration_data):

    if not name.startswith('_'):
        value = getattr(sensor.calibration_data, name)

        if isinstance(value, int):
            print('{}: {}'.format(name, value))

# These oversampling settings can be tweaked to
# change the balance between accuracy and noise in
# the data.

sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)

print('\n\nInitial reading:')
for name in dir(sensor.data):
    value = getattr(sensor.data, name)

    if not name.startswith('_'):
        print('{}: {}'.format(name, value))

sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150)
sensor.select_gas_heater_profile(0)

# Up to 10 heater profiles can be configured, each
# with their own temperature and duration.
# sensor.set_gas_heater_profile(200, 150, nb_profile=1)
# sensor.select_gas_heater_profile(1)
n=0
print('\n\nPolling:')
try:
    while n<5:
        if sensor.get_sensor_data():
            output = '{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH'.format(
                sensor.data.temperature,
                sensor.data.pressure,
                sensor.data.humidity)

            if sensor.data.heat_stable:
                print('{0},{1} Ohms'.format(
                    output,
                    sensor.data.gas_resistance))

            else:
                print(output)
	
        time.sleep(1)
        n=n+1

except KeyboardInterrupt:
    pass


temp_bmp = sensor.data.temperature
hum_bmp = sensor.data.humidity
press_bmp = sensor.data.pressure
gas_bmp = sensor.data.gas_resistance

############################CO2#########################################
import mh_z19
co2read =  mh_z19.read_all()
co2 = co2read["co2"]
temp_co2 = co2read["temperature"]
print(co2,temp_co2)

############################RasPi#########################################

############################Ambient#####################################
import ambient
ambi = ambient.Ambient(チャネルId, ライトキー)
r = ambi.send({"d1": temp_bmp, "d2": hum_bmp, "d3": press_bmp, "d4": gas_bmp, "d5": co2, "d6": temp_co2, "d7": humid, "d8": humid})

定期実行

crontabにて5分毎に上記プログラムを実行するように設定しました。Ambientサーバーへの送信も5分に一度行われるようになっています。

*/5 * * * * python3 /home/pi/bme680_and_CO2.py

モニタリング状況

●PCから確認
f:id:tara-chang:20210131193303p:plain
スマホから確認
f:id:tara-chang:20210131193315p:plain
無事各種センサーの測定値がモニタリング出来ました!

課題

・BME680の有機ガスセンサーはセンサー値が安定するまで時間を要します。今のプログラムではその辺が上手く取り扱えていません。
・CO2濃度が一定値超えたらアラート…みたいな仕組みは今のところありません。Lineで通知しても良いかもしれませんね!
・サーバーへの情報送信、センサー測定のレートなどがどの程度の塩梅で行うのが省エネなのか、良く分かりません…。