Cool Arduino MIDI Projects
Arduino+MIDIのかっこいいプロジェクトを集めてみました。
LOOK MUM NO COMPUTERの工作は圧倒的ですね。工作過程も出してくれているので大変勉強になります。
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); } }
動作
— タラオメタル (@chem_phys_elec) May 8, 2021
プログラム通りの順にドラム音が出ました。ここまで出来たら原理的には何でも作れそうな感じがしちゃいますね。
パターン化することでドラムマシンや電子ドラムが作れます。プラグインをシンセにすればシンセがそのまま鳴らせるので自動演奏器のようにしても面白いかもしれません。
今後
ドラムマシン、電子ドラム、自動演奏器、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
無事ディスプレイに最新のダスト密度が表示されました。
コード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へも送信されていることが確認出来ました。
しかし問題として、前回Arduinoで測定したダスト密度より一桁高くなってしまっていることや、S/Nが悪くなっています。プログラムに修正が必要なものと思われます。
SHARPほこりセンサーGP2Y1010AUをArduinoで動作させる
室内のほこり濃度を可視化して一定値を超えたら換気したりルンバで掃除したりするというスマートハウスの一機能を構想しています。まず第一歩としてホコリセンサーを触ってみることにしました。どの程度の感度があるのか、目的のような使い方が出来るのかを知りたいです。今回はArduinoによる動作確認と、ほこりへの応答を確認するところまでとなります。
ほこりセンサーの調達
SHARPのほこりセンサーGP2Y1010AUを使用しました。大きな貫通穴が開いた金属筐体が特徴で、大きさはマッチ箱くらいです。空気清浄機なんかにも使用されているようです。秋月電子で900円でした。Alieepressでも同様の製品が安価で入手できそうです。
購入後に気づいたのですが、精度が向上したGP2Y1014AU0Fモデルやデジタル出力が可能なタイプもあるようです。
センサー原理
データシートもしくはsharpsensoruserにてに詳しい説明があります。貫通穴を通って流入する空気に対してLEDより光が照射され、ほこりによって散乱された光を検出器で検出し、信号を増幅して出力するようです。ほこり濃度によってどの程度の信号電圧が得られるかはデータシートに記載されています。
ほこり濃度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
動作確認
ホコリ密度が出力されました。試しにセンサー付近でほこりをかぶったクリーナーをパタパタさせるとパルス状の信号の増加が見られました。結構敏感に動作しているようです。
ホコリセンサーテストの様子。 pic.twitter.com/ZbhXxnDG36
— タラオメタル (@chem_phys_elec) February 11, 2021
今後やること
・回路、プログラムの観点から測定精度を高める。
・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>『となりのヤングジャンプ』で連載中。(2012年6月14日〜)</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>『裏サンデー』で連載中。(2012年4月18日〜)</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&iid=2">第1話</a> <a href="/fc2-imageviewer/?aid=1&iid=3">第2話</a> <a href="/fc2-imageviewer/?aid=1&iid=4">第3話</a> <a href="/fc2-imageviewer/?aid=1&iid=5">第4話</a> <a href="/fc2-imageviewer/?aid=1&iid=6">第5話</a> <a href="/fc2-imageviewer/?aid=1&iid=7">第6話</a> <a href="/fc2-imageviewer/?aid=1&iid=8">第7話</a> <a href="/fc2-imageviewer/?aid=1&iid=9">第8話</a> <a href="/fc2-imageviewer/?aid=1&iid=10">第9話</a> <a href="/fc2-imageviewer/?aid=1&iid=11">第10話</a><br/> ・ ・ ・ =1&iid=122">第121話</a> <a href="/fc2-imageviewer/?aid=1&iid=123">第122話</a> <a href="/fc2-imageviewer/?aid=1&iid=124">第123話</a> <a href="/fc2-imageviewer/?aid=1&iid=125">第124話</a> <a href="/fc2-imageviewer/?aid=1&iid=126">第125話</a> <a href="/fc2-imageviewer/?aid=1&iid=127">第126話</a> <a href="/fc2-imageviewer/?aid=1&iid=128">第127話</a> <a href="/fc2-imageviewer/?aid=1&iid=129">第128話</a> <a href="/fc2-imageviewer/?aid=1&iid=131">第129話</a> <a href="/fc2-imageviewer/?aid=1&iid=132">第130話</a><br/> <a href="/fc2-imageviewer/?aid=1&iid=133">第131話</a> <a href="/fc2-imageviewer/?aid=1&iid=134">第132話</a> <a href="/fc2-imageviewer/?aid=1&iid=135">第133話</a> <a href="/fc2-imageviewer/?aid=1&iid=136">第134話</a> <a href="/fc2-imageviewer/?aid=1&iid=137">第135話</a> <a href="/fc2-imageviewer/?aid=1&iid=138">第136話</a> <a href="/fc2-imageviewer/?aid=1&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&iid=2">第1話</a>, <a href="/fc2-imageviewer/?aid=1&iid=3">第2話</a>, <a href="/fc2-imageviewer/?aid=1&iid=4">第3話</a>, <a href="/fc2-imageviewer/?aid=1&iid=5">第4話</a>, <a href="/fc2-imageviewer/?aid=1&iid=6">第5話</a>, <a href="/fc2-imageviewer/?aid=1&iid=7">第6話</a>, <a href="/fc2-imageviewer/?aid=1&iid=8">第7話</a>, <a href="/fc2-imageviewer/?aid=1&iid=9">第8話</a>, <a href="/fc2-imageviewer/?aid=1&iid=10">第9話</a>, <a href="/fc2-imageviewer/?aid=1&iid=11">第10話</a>, <a href="/fc2-imageviewer/?aid=1&iid=12">第11話</a>, <a href="/fc2-imageviewer/?aid=1&iid=13">第12話 ・ ・ aid=1&iid=134">第132話</a>, <a href="/fc2-imageviewer/?aid=1&iid=135">第133話</a>, <a href="/fc2-imageviewer/?aid=1&iid=136">第134話</a>, <a href="/fc2-imageviewer/?aid=1&iid=137">第135話</a>, <a href="/fc2-imageviewer/?aid=1&iid=138">第136話</a>, <a href="/fc2-imageviewer/?aid=1&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
動作
こんな感じで通知が来ます。実際に更新通知はまだ来てないですが、来る日を楽しみにしています!
※追記 動作確認出来ました。
先月作ったワンパンマン更新通知の動作確認がようやく出来た(笑) https://t.co/QVpGphx1pJ pic.twitter.com/J52aAgsUkZ
— タラオメタル (@chem_phys_elec) March 6, 2021
Arudino Nano + ミニUSBホストシールドを試す
前回の記事ではArduino UnoとUSBホストシールドを使用しましたが、デバイス小型化のためにArduino Nanoを用いることが出来ないかと考えました。探してみるとArduino Nanoで使えそうな「ミニUSBホストシールド(Mini USB Host Shield)」なるものが見つかりました。
ただよく読むと、こちらArduino NanoではなくArduino Pro microやAruduino Pro Miniというボードに対応してるみたいです。
それでも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+ミニUSBホストシールドでも動いた…!これで大幅に小型化出来るぞ。
— タラオメタル (@chem_phys_elec) January 24, 2021
めっちゃ諦めかけてたけど諦めなくてよかった。 pic.twitter.com/FuZrSJMYp6
教訓
ミニUSBホストシールドはArduino Nanoと接続するにはピン配置が違いすぎます。おとなしくPro miniを使うかPro Microを使うべきでしょう。しかしこれらのボードはNanoよりもGPIOピンの数が少ないのでケースバイケースとも言えます。
ラズパイでCO2/気圧/温度/湿度/ガスの総合測定+モニタリング【Ambient】
以前の記事で複合センサーBME680とCO2センサーMH-Z19BをRaspberry Piで使用するところまではやりました。
jakejake.hatenablog.com
jakejake.hatenablog.com
今回はそれらのセンサーの取得値(CO2/気圧/温度/湿度/有機ガス)をまとめて測定し、スマホから好きな時に確認出来るようにしたいと思います。
配線
ラズパイ側 | 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から確認
●スマホから確認
無事各種センサーの測定値がモニタリング出来ました!