M5StackをBLEキーボードにしてみた

5月もそろそろ終わります。
博士論文をかきはじめました。


3ボタンくらいのお手軽なキーボードが欲しかったので
家に余っていた M5StackをBluetoothキーボード化してみました。

といっても、今は便利なライブラリが出ているようでしたので

programresource.net

こちらのブログを参考にさせていただきました。
(というかほぼコピペ)

あえて僕の書いたソースコードを載せるまでもありませんが
キーアサインについて下記にまとめておきます。

これらは、使ったライブラリのBleKeyboard.hに書いてある内容です。

const uint8_t KEY_LEFT_CTRL = 0x80;
const uint8_t KEY_LEFT_SHIFT = 0x81;
const uint8_t KEY_LEFT_ALT = 0x82;
const uint8_t KEY_LEFT_GUI = 0x83;
const uint8_t KEY_RIGHT_CTRL = 0x84;
const uint8_t KEY_RIGHT_SHIFT = 0x85;
const uint8_t KEY_RIGHT_ALT = 0x86;
const uint8_t KEY_RIGHT_GUI = 0x87;

const uint8_t KEY_UP_ARROW = 0xDA;
const uint8_t KEY_DOWN_ARROW = 0xD9;
const uint8_t KEY_LEFT_ARROW = 0xD8;
const uint8_t KEY_RIGHT_ARROW = 0xD7;
const uint8_t KEY_BACKSPACE = 0xB2;
const uint8_t KEY_TAB = 0xB3;
const uint8_t KEY_RETURN = 0xB0;
const uint8_t KEY_ESC = 0xB1;
const uint8_t KEY_INSERT = 0xD1;
const uint8_t KEY_DELETE = 0xD4;
const uint8_t KEY_PAGE_UP = 0xD3;
const uint8_t KEY_PAGE_DOWN = 0xD6;
const uint8_t KEY_HOME = 0xD2;
const uint8_t KEY_END = 0xD5;
const uint8_t KEY_CAPS_LOCK = 0xC1;
const uint8_t KEY_F1 = 0xC2;
const uint8_t KEY_F2 = 0xC3;
const uint8_t KEY_F3 = 0xC4;
const uint8_t KEY_F4 = 0xC5;
const uint8_t KEY_F5 = 0xC6;
const uint8_t KEY_F6 = 0xC7;
const uint8_t KEY_F7 = 0xC8;
const uint8_t KEY_F8 = 0xC9;
const uint8_t KEY_F9 = 0xCA;
const uint8_t KEY_F10 = 0xCB;
const uint8_t KEY_F11 = 0xCC;
const uint8_t KEY_F12 = 0xCD;

プレゼンテーションに使ったり、よく使うショートカットキーを
登録しておいたりできれば便利そうですね

ちなみに、英数字キーのそれぞれの16進数表記は'BleKeyboard.cpp'内に書いてありました。

M5StackでCO2濃度の監視モニタを作ってみた

緊急事態宣言がいよいよ解除になりました。 第二波も怖いですし、引き続き新しい生活様式での生活が求められる昨今であります。

自分は全面的にリモートワークになっているのですが、現場でしかできない作業もあるので ソーシャルディスタンスを厳守した上で、定期的に研究所へ出勤することになりそう。


リモートワークでの仕事は、自宅にもデュアルディスプレイやら
ゲーミングチェア、HHKBといった作業環境は整えてあるので
デスクワークの作業効率自体はそこまで落ちていないのですが
時折集中力が続かなくなります。

原因は気圧のせいだったり色々あるのですが、部屋のCO2濃度も集中力を乱す1つの原因となります。

てことで、昔買って積んであったCO2センサーを使って、CO2濃度の監視モニターを作ってみました。


欲しい機能

・測定したCO2濃度を画面に表示したい
・できたらリアルタイムでグラフもプロット
・ambientといったクラウドサービスは使いたくない
・一定数値以上になったら、LINEに換気を促す通知を送りたい

ということで、これらの機能を満たすようなCO2濃度監視モニタを作ってみました。

使用したCO2センサーはAliexperessで購入

ja.aliexpress.com

MH-Z19Bという、この界隈ではよく使われる格安センサーです。
送料込みで20ドル程度で購入しました。


数値のリアルタイムプロットは、こちらのサイトを参考にさせていただきました。

magazine.halake.com

LINEへの通知は、過去に作ったこれを流用しています。

tmegane.hatenablog.com


ソースコード

コードはこんな感じ。
測定したCO2濃度が1000ppmを超えたら表示を黄色時に 1500ppmを超えたら、赤字にするかつLINEへ通知を送ります。 一度LINEに通知を送った後は、
濃度が1500ppmを下回ってから、再度上昇した時に再び通知を送るような仕様にしています。 そうしないと、1500ppmを超えたら無限に通知が来ることになっちゃいますからね。

1000ppmは厚生労働省の建築物環境衛生管理基準値
1500ppmは文部科学省の学校環境衛生基準値をそれぞれ採用しています。

#include "MHZ19.h"

#include <ssl_client.h>
#include <WiFiClientSecure.h>
#include <M5Stack.h>


// 表示に関係する定義
#define WIDTH 320
#define HEIGHT 240
#define GRAPH_X 25
#define GRAPH_Y 50
#define GRAPH_SPACE 2
#define GRAPH_W WIDTH - GRAPH_X - GRAPH_SPACE
#define GRAPH_H HEIGHT - GRAPH_Y - GRAPH_SPACE
#define GRAPH_MAX 3500
#define GRAPH_MIN 200


TFT_eSPI tft = TFT_eSPI();

MHZ19 myMHZ19;
HardwareSerial mySerial(2);

int flag=0;



uint16_t GraphBuff[int(GRAPH_W)] = {0};
uint16_t graphStartPos[2] = {
  GRAPH_X - 1,
  GRAPH_Y - 1 + int(GRAPH_H)
};


void wifi_connect() {
  const char* ssid = "SSID"; //Wifi SSID
  const char* passwd = "password"; //Wifi passwd
  WiFi.begin(ssid, passwd);

  while (WiFi.status() != WL_CONNECTED) { //Wifi 接続待ち
    delay(500);
  }

  Serial.println(WiFi.localIP());
}



void send_bot(int CO2_value) {

  const char* host = "notify-api.line.me"; // line_notifyのURL
  const char* token = "LINE API トークンキー"; //トークンキー

  const char* message1 = " ppm";
  const char* message2 = "換気しよう!";

  
  WiFiClientSecure client;
  if (!client.connect(host, 443)) {
    Serial.print("error1");
    return;
  }
  String query = String("message=") + String(CO2_value) + message1 + "\n" + message2;
  String request = String("") +
                   "POST /api/notify HTTP/1.1\r\n" +
                   "Host: " + host + "\r\n" +
                   "Authorization: Bearer " + token + "\r\n" +
                   "Content-Length: " + String(query.length()) +  "\r\n" +
                   "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                   query + "\r\n";
  client.print(request);
  while (client.connected()) { //受信待機
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }

  Serial.print(request);


}


// -------  グラフに表示 -------

void slideBuff(uint16_t buff[], uint16_t size){
  for(int i = size - 1; i >= 0; i--) buff[i] = buff[i - 1];
}



void drawText(uint32_t x, uint32_t y, String text, uint32_t color = -1, uint8_t size = 1){
  M5.Lcd.setTextColor(color);
  M5.Lcd.setTextSize(size);
  M5.Lcd.setCursor(x, y);
  M5.Lcd.print(text);
}



void updateGraph(){
  M5.Lcd.fillRect(GRAPH_X + 1, GRAPH_Y + 1, GRAPH_W - GRAPH_SPACE, GRAPH_H - GRAPH_SPACE, 0);
  for(int i = 0; i < sizeof(GraphBuff); i++){
    M5.Lcd.drawPixel(graphStartPos[0] + i, graphStartPos[1] - GraphBuff[i], -1);
  }
}



void setupGraph(){
  M5.Lcd.drawRect(GRAPH_X, GRAPH_Y, GRAPH_W, GRAPH_H, -1);
  drawText(0, HEIGHT - 10, String(GRAPH_MIN), -1, 1);
  drawText(0, HEIGHT - GRAPH_SPACE - (int(GRAPH_H) / 2), String(((GRAPH_MAX - GRAPH_MIN) / 2) + GRAPH_MIN), -1, 1);
  drawText(0, HEIGHT - GRAPH_SPACE - int(GRAPH_H), String(GRAPH_MAX), -1, 1);
}

// -------  グラフに表示ここまで -------

void setup() {
 
  M5.begin();
  wifi_connect();
  
  Serial.begin(115200);
  mySerial.begin(9600, SERIAL_8N1, 16, 17);
  myMHZ19.begin(mySerial);
  setupGraph();



}


void loop() {
  
  M5.update();
 // tft.fillScreen(BLACK);

  
  
  int CO2 = myMHZ19.getCO2();
  int8_t Temp = myMHZ19.getTemperature();
  Serial.println("CO2 (ppm): " + String(CO2) + "\tTemperature (C): " + String(Temp));

  if(CO2 >= 1000 && CO2<1500){
    tft.setTextColor(YELLOW,BLACK);
    tft.setTextSize(3);
    tft.setCursor(3, 20);
    tft.println("CO2: " + String(CO2) + " ppm ");
  }else if(CO2 >= 1500) {
    tft.setTextColor(RED,BLACK);
    tft.setTextSize(3);
    tft.setCursor(3, 20);
    tft.println("CO2: " + String(CO2) + " ppm ");

    if(flag==0){
      send_bot(CO2);      
      flag=1;
    }
  
  } else {
    tft.setTextColor(WHITE,BLACK);
    tft.setTextSize(3);
    tft.setCursor(3, 20);
    tft.println("CO2: " + String(CO2) + " ppm ");
    flag=0;
  }

  slideBuff(GraphBuff, sizeof(GraphBuff) / 2); 
  GraphBuff[0] = map(CO2, GRAPH_MIN, GRAPH_MAX, 0, GRAPH_H - 2);
  updateGraph();

  
  delay(500);

  


}


M5StackとMH-Z19Bとの接続は簡単で、ジャンパ線で線を4本つなぐだけ。

M5Stack MH-Z19B
GND GND
5V Vin
TXD RX
RXD TX

動作させてみるとこんな感じ。

f:id:Tmegane:20200525144000j:plain:w200 f:id:Tmegane:20200525144022j:plain:w200f:id:Tmegane:20200525144010j:plain:w200

1500ppmを超えるとLINEに通知が来ます。

f:id:Tmegane:20200525144306p:plain

ちなみに、このセンサーは電源をいれてからヒーターが安定するまで3分程度かかります。
安定するまでは、突然濃度が高い値になり、LINEに通知を送ってしまうことがあるので
気になる人は電源をいれてから3分間は通知を送らないようなロジックを入れ込んでも良いと思います。


基板にしてみた

PCBWayのポイントが余っていたので、プリント基板にしました。

f:id:Tmegane:20200525144626j:plain:w300

M5Stackの横に挿して使うだけの簡単な基板です。

この様に、机の上に立てて使うことができます。
ジャンパ線もなくなり、すっきりしました。
f:id:Tmegane:20200525144713j:plain:w300

基板は例によって10枚作って余っているので、 気になる人がいたらTwitterかメールで連絡ください。
送料+100円分くらいのamazonギフト券 or Paypayで送ります。

RSSで論文チェックをスピーディーにしてみた

4月からD3になります。公募に出し始めないと・・・


博士号を取得した後だけでなく、研究室に配属された学部生や大学院生にとっても
自分の研究に関連するジャーナルの新着論文をチェックするのは義務といっても過言ではありません。

原子核実験系の自分だと、PRLの原子核パートとPRC、そして装置系のジャーナルである
NIMのAとBは最低限必要で、それに加えてもっとコアなところをいくつかチェックしています。

チェックする論文誌が増えれば増えるほど、サイトの巡回に時間がかかってしまうわけですが
少しでも楽をして、その分の時間を研究時間に充てたいと思うのが院生の性。

ということで、RSS feedlyを使って楽をしてみました。

RSSとは

RSSとは、Really Simple Syndication の略で(いくつか他の名称も有)
ブログやニュースサイトの更新情報を配信するための文章フォーマットのことを言います。

10年くらい前にあった個人ブログの全盛期にはとても良く使われていた技術ですが
最近はSNSの普及等もあり、枯れた技術になりつつあります。
なんでもFirefoxは2年くらい前に対応をやめたらしい・・・・

RSS feedly

RSSを使うために、feedlyというサービスに登録します。

feedly.com

f:id:Tmegane:20200205120009p:plain:w600

GET STARTED FOR FREEをクリックして、アカウントを作ります。

f:id:Tmegane:20200205120145p:plain
僕はめんどくさいのでTwitterと連携させました。ツイ廃ばんざい。

登録したら、RSSを取得したい記事を探します。
検索ボックスにジャーナル名を取得するといくつか候補を出してくれます。
なかったら地道に探しましょう(PRCはすぐに出てこなかった・・・)

f:id:Tmegane:20200205120304p:plain

読みたいものを見つけたら、フォローします。
NEW FEEDを押すと、自分で決めたカテゴリにわけて保存してくれます。
僕はとりあえずpaperっていうFEEDを作って、そこに放り込んでいきました。
f:id:Tmegane:20200205120603p:plain

f:id:Tmegane:20200205120756p:plain  次々にFEEDに追加していくと、こんな感じになります。paperタブをクリックすると 画面に今日の更新分がずらっと表示されるように。
気になった記事があったら、クリックするとアブストラクトが表示されます。

f:id:Tmegane:20200205120905p:plain

DLしたりとか、詳細が見たかったら元サイトに飛んで確認。

こんな感じで、日々のジャーナルサイト巡回が短縮できます。 feedlyスマホアプリもあるので、出先でスマホからもサクッと確認できます。

あ、もちろん論文以外でも普通のニュースサイトやらでも使えますよ。

3Dプリンタで遊んでみる

先月は出張月間でした。
ドイツ -> 岐阜 -> 山形 -> 福島という出張ルート。
山形では日本酒をたくさん飲みました。一番高かったのは
十四代の秘蔵酒・・・参考価格は楽天で4合43200円。
偉い人に感謝。

福島では福島第一原発の視察にいってきました。  


さて、数日前に仕事場に3Dプリンタを導入しました。
(実は購入したのは春で、箱からも出されずに放置されていた・・・)

購入したのは僕が気になっていたアドベンチャー3。
adventurer3 | 3Dプリンター | フラッシュフォージ
いつぞやに裏メイカー祭で実物を見て、メイカー達が
口を揃えて使い勝手がいいと推していたので、その流れ乗ってみました。
値段は6万円くらい。家庭用としても手が出るレベル?

f:id:Tmegane:20191015130030j:plain:w300 f:id:Tmegane:20191015130018j:plain:w300

一緒に買ったのはABSのフィラメント。
f:id:Tmegane:20191015130041j:plain:w300
そういえば導電性のフィラメントなるものもあるらしい。便利そう。

さっそくセットアップしてみて、適当な箱を印刷。
f:id:Tmegane:20191015131017j:plain:w300
こいつ・・・動くぞ・・・!

3時間弱ほど待機してできたのがこんな感じ。
f:id:Tmegane:20191015130652j:plain:w300

おお、なかなか良く出来てるじゃないか!
と、剥がしてみたら

f:id:Tmegane:20191015130704j:plain:w300

裏面にヒビが・・・
どうやら背が高いものを印刷するときは温度を少し高めにしたほうが良いらしい。 (このときはデフォルトの225℃で印刷)

てことで、印刷温度を230℃に変更して再び印刷。
f:id:Tmegane:20191015130050j:plain:w300
今度はうまくできました。

この程度の3Dモデルなら10分もあればデザインできますし
印刷も数時間で済むのでとてもお手軽。かんたんな治具とかを作るのにとても役立ちそうですね

しかし、温度設定をはじめとして、色々と使っていかないとわからないことも多そうなので
思い立ったら適当に印刷してみて、ノウハウを増やしていきたいところ。

あ、印刷したやつを剥がすときにはスクレーパーがあると便利でした。 (最初持っていなくて、カッターナイフで頑張った)

M5StackからLINEに投稿する

関東地方の梅雨があけたようです。
クソ暑い・・・


この間、Line notifyというのを導入したので、
M5Stackから投稿するテストをしてみました。

tmegane.hatenablog.com

Micropythonだとrequestモジュールがそのまま使えなかったので、
Arduino言語を使いました。どうも開発環境を統一できていない・・・

#include <ssl_client.h>
#include <WiFiClientSecure.h>
#include <M5Stack.h>

void setup() {
  M5.begin();
  wifi_connect();
  send_bot();

}

void loop() {
  M5.update();
}

void wifi_connect() {
  const char* ssid = "SSID"; //Wifi SSID
  const char* passwd = "password"; //Wifi passwd
  WiFi.begin(ssid, passwd);

  while (WiFi.status() != WL_CONNECTED) { //Wifi 接続待ち
    delay(500);
  }

  M5.Lcd.println(WiFi.localIP());

}



void send_bot() {

  const char* host = "notify-api.line.me"; // line_notifyのURL
  const char* token = "token"; //トークンキー
  const char* message = "from M5stack"; //送るmessage

  WiFiClientSecure client;
  if (!client.connect(host, 443)) {
    M5.Lcd.print("WiFi connection error");
    return;
  }
  String query = String("message=") + "\n" + message;
  String request = String("") +
                   "POST /api/notify HTTP/1.1\r\n" +
                   "Host: " + host + "\r\n" +
                   "Authorization: Bearer " + token + "\r\n" +
                   "Content-Length: " + String(query.length()) +  "\r\n" +
                   "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                   query + "\r\n";
  client.print(request);
  while (client.connected()) { //受信待機
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }

  M5.Lcd.print(request);


}

特に便利なライブラリもないので、Line notifyにhttps通信でダイレクトに叩いてます。
requestの内容はLine notifyの公式APIを参照。
目視でも確認できるように、送ったらLCDへ表示させるようにしています。

M5Stackが起動したらWifiに接続するまで待機し、LCDに接続したIPアドレスを表示。
そしてLineへとmessageを送ります。 f:id:Tmegane:20190730135749p:plain:w400

よし。

LINE Notifyを使ってPythonからLINEにメッセージを送る

夏です、暑いです。
気がついたら来週末はメイカーフェアではありませんか。
チケットを買いに行かないと・・・  


自分たちで作った色々なbotとか、解析プログラム等のタスクが終了したときのステータス報告の投稿先として pythonからslackの任意のチャンネルへと投げています。

tmegane.hatenablog.com

別にslackでも特に問題はないのですが、LINEでも同じようなことができると知ったので試してみました。

必要なもの

requestsモジュール
Line notify notify-bot.line.me

トークンキーを発行する

Line notifyのマイページからトークンキーを発行します。
f:id:Tmegane:20190728220145p:plain:w500

Generate access tokenから、トークンを作りたいLineグループを選択し
botの名前を入力して、Generate tokenをクリック。 f:id:Tmegane:20190728220354p:plain:w400
すると、トークンキーが発行されるのでメモしておきます。一度発行したトークンキーを再度表示させることはできないようなので
メモをなくしたら再発行するしか方法がないようです。

グループにbotを追加させる

トークンキーを発行したら、botの投稿先のLineグループへと、bot本体(Line notify)を招待します。
招待の仕方は通常のLINEグループに人を招待する方法と全く同じです。 f:id:Tmegane:20190728220813p:plain:w400

招待したら、LINE側での設定は完了です。

プログラムを書く

slackへbotから投稿させる場合は、slackweb等といったモジュールをいれる必要がありますが
lineの場合は汎用的なrequestsモジュールだけでOKです。
入ってなかったらpip install requestsとかでいれておきましょう。

Pythonで書いたプログラムはこんな感じ

import requests

line_notify_token = 'token' # 発行したトークンキーを入力
line_notify_api = 'https://notify-api.line.me/api/notify'
message = 'Test' #送るメッセージ


payload = {'message': message}
headers = {'Authorization': 'Bearer ' + line_notify_token} 
line_notify = requests.post(line_notify_api, data=payload, headers=headers)

コードの長さはslackへ投稿するのとさほど変わらないですね。
実行するとLINEへ通知がとんできます。

f:id:Tmegane:20190728221223p:plain:w400

使い勝手も悪くなさそうなので、プロダクトに応じてslackとLINEを使い分けてみることにします

ROOTでゲートをかけた部分だけのrootファイルを作る

東海村にいます。後輩の指導と自分自身のお勉強。
田舎なんだろうなあって覚悟しながらきたのですが、思っていた数倍は快適です。
余計なことを考えずに研究をするにはかなり良い環境かもしれない・・・


CERN ROOTで、ゲートをかけた一部分だけのtreeだけを別のrootファイルに書き出す方法の覚え書き。

#include <stdlib.h>
#include <string.h>
#include <fstream>
#include <iostream>

using namespace std;

void toROOT(){

    TFile *infile  = new TFile("元のrootファイル.root");
    TFile *fout = new TFile("新しく作りたいrootファイル.root","recreate");
    ((TTree*)infile->Get("treeの名前"))->CopyTree("書き出す条件");
    fout->Write();
    fout->Close();
}

CopyTree内で指定した条件に合致しているデータのみを新しいrootファイルとして書き出してくれます。
ほしい領域だけにゲートをかけたrootファイルが作れるので、処理が軽くなって便利。
GUIのToolBarにあるハサミマークからカットした領域、CUTGだけを書き出すことも可能。