Raspberry Pi pico Wをスマホから遠隔操作する。
やりたいこと
以下の通り。なお、Wi-Fiアクセスポイントは、iPhoneによるテザリングを想定している。
- Raspberry Pi pico Wが移動していると仮定する。
- Raspberry Pi pico Wが、登録しておいたWi-Fiアクセスポイントに近づいたら、自動で接続する。
- Wi-Fiアクセスポイントに接続できたら、SLACKに通知する。
- 通知をみら、指定されたURLにアクセスすると、ブラウザにON/OFFボタンが表示される。
- ボタンをクリックすると、出力端子が作動する。
コード
import machine
import network
import uasyncio
import urequests
from microdot import Microdot, Response
SSID = "YOUR_WI-FI_SSID"
PW = "WI-FI_PASSWORD"
SLACK_URL = "https://hooks.slack.com/services/***********/***********/************************"
async def run_web_server():
app = Microdot()
Response.default_content_type = 'text/html'
htmldoc_home = "<html><head><style type="text/css"><!-- table {width: 100%; height: 100%;} td {text-align: center;} button {width: 90%; height: 80%; font-size: 64px;} --></style><title>Wireless controller</title></head><body><table><tr><td><button onclick="location.href='./relay/on'">On</button></td><td><button onclick="location.href='./relay/off'">Off</button></td></tr></table></body></html>"
htmldoc_on = "<html><head><title>Wireless controller</title></head><body>Turned on.</br><a href="../../">Return</a></body></html>"
htmldoc_off = "<html><head><title>Wireless controller</title></head><body>Turned off.</br><a href="../../">Return</a></body></html>"
relay1 = machine.Pin(21, machine.Pin.OUT)
relay2 = machine.Pin(22, machine.Pin.OUT)
relay1.off()
relay2.off()
@app.get('/')
async def _index(request):
return htmldoc_home
@app.get('/relay/<status>')
async def _relay(request, status):
if status == 'on':
relay1.on()
relay2.on()
return htmldoc_on
elif status == 'off':
relay1.off()
relay2.off()
return htmldoc_off
return 'Invalid status.'
print('Web server is running.')
app.run(port = 80)
async def loop():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
led = machine.Pin('LED', machine.Pin.OUT)
while True:
if wlan.isconnected() == False:
print("Connecting to the Wi-Fi router.", end="")
count = 0
while wlan.isconnected() == False:
if count == 0:
wlan.disconnect()
wlan.connect(SSID, PW)
led.value(1)
print(".", end="")
await uasyncio.sleep(0.25)
led.value(0)
await uasyncio.sleep(0.25)
count += 1
if count > 10:
count = 0
print(" Connected.")
led.value(1)
wlan_status = wlan.ifconfig()
print(f'- IP Address: {wlan_status[0]}')
print(f'- Netmask: {wlan_status[1]}')
print(f'- Default Gateway: {wlan_status[2]}')
print(f'- Name Server: {wlan_status[3]}')
print()
print('Please access: http://{}/'.format(wlan.ifconfig()[0]))
print()
print('I will nortify to the Slack.')
data = {"text" : "Hello! Please access: http://" + wlan_status[0] +"/", "username" : "Raspberry Pi"}
response = urequests.post(SLACK_URL, json=data)
print(f'{response.status_code} {response.text}')
response.close()
await uasyncio.sleep(1)
async def main():
uasyncio.create_task(loop())
await run_web_server()
if __name__ == '__main__':
uasyncio.run(main())
参考になったサイト
記事を参考にRaspberry Pi Zero Wにumap2をインストール
守
基本的にはこの記事のとおりである。 qiita.com
ただし、一部を自分で読み替えたため、違う点を以下にメモしておく。
破
ポイント1(機材)
以下の機器を用意する。
- Raspberry Pi Zero WH
- microUSB OTGケーブル
- miniHDMI(m)-HDMI(f)変換アダプタ
- SDカード 32GB
- USBハブ
- USBマウス
- USBキーボード
- microUSBケーブル(電源用)
- ラップトップPC
- USB接続SDカードライター
加えて、ソフトウェアをインターネットからダウンロードするためのWi-Fi環境も必要である。
- Raspberry Pi Zero 2 Wを購入してはいけない。なぜならば、後にstart_gadgetfs_RaspiZeroW.shを実行するときに、
unknown filesystem typeというエラーがでてしまうため。 - SDカードは、SDHDカードの上限である32GBを選ぶのが無難。64GB以上は規格がSDXDになり、書き込みに失敗するという記事が存在する。
- ヒートシンクが必要という記事が存在するが、この用途だけなら不要と考える。
- SDカードライターはPCに備え付けのものがあれば最低限は事足りるが、作成したOSイメージをSDカードへバックアップするときのため、外付けのものを用意するとよい。
ポイント2(SDカードへの書込み その1)
"2022-09-22-raspios-bullseye-armhf-full.img.xz"という古いイメージをダウンロードし、Raspberry Pi imagerの「カスタムイメージを使う/Use custom」というメニューでSDカードへ書き込む。
- 最新のRecommendedのOSを選択してはいけない。Umap2は、2016年ごろに書かれたプログラムである。最新のRaspberry Pi OSを選択してしまうと、umap2のインストールするときに用いるpython-devやpython-setuptoolsが容易にインストールできない。最新のRaspberry Pi OSには、Pythonの2系がインストールしにくいため。
- 私の手元の環境だと、SDカードへの書き込みに何度も失敗する。根気よく繰り返す。
ポイント3(SDカードへの書込み その2)
SDカードをRaspberry Piに挿入する前に、SDカードの中のファイルconfig.txtの末尾の[all]の下に、以下を追記する。
dtoverlay=dwc2
記事では、
enable_uart=1も追記しているが、USB-UART変換ケーブルを持っていないのであれば、追記不要である。Raspberry Pi Zero WにはWi-Fi経由でのssh接続できるため。ただし、sshで接続するにはWi-Fiルーター(スマホのテザリングでも代用可)が必要なため、初心者にも使いやすい手順書にしたい場合は、変換ケーブルを購入するとよい。 www.monotaro.comRaspbery PiのUSBポートは、ホストとして駆動している。一方、umap2によりほかのデバイスのUSBポートをスキャンするときは、Raspbery PiのUSBポートをクライアントにしておかなければならない。dwc2を追記しておくと、クライアントとしても使用できるようになる。
- 公式ページの以下の記述によると、cmdline.txtに
modules-load=dwc2を追記するようにも指示があるが、こちらは追記しなくても機能する。
umap2/gadget at master · nccgroup/umap2 · GitHub
ポイント4(初回セットアップ)
SDカードをRaspberry Piに挿入してから、USBポート(電源用は外側)にUSBケーブルを挿入する。
初回起動時のセットアップにおいて、海外の従業員がそのまま読めるよう、「Use English Language」にチェックを入れる。
アカウントは、伝統的にユーザー名に「pi」、パスワードに「raspberry」を設定する。
ソフトウェアのインストールや、Wi-Fi経由でのSSH接続ができるよう、Wi-Fiの設定をしておく。(Raspberry Pi ZeroにはUSBポートが実質1個しかないので、USBポートをスキャンするときは、USBキーボードやマウスが接続できなくなる。)
アップデートはスキップする。
ポイント5(パッケージリストの更新)
初回起動時は数分間待つ。
そのあと、パッケージリストを更新する。
sudo apt update
- 初回起動の直後はpackagekitdというプロセスが実行されていて、apt updateに失敗するため。数分間待ってから実行する。
- apt upgrade は実行しなくてよい。
ポイント6(Pytyon 2系のインストール)
Pythonの2系をインストールする。
sudo apt -y install python-dev sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 sudo update-alternatives --config python
sudo update-alternatives --config pythonをした後の出力が記事とは違う。2.7系しかインストールしていないため、選択肢が表示されない。1つしか表示されないので、この手順は不要な気がする。要追加検証。
pythonのバージョンが2.7.1になっていることを確認する。
python --version
ポイント7(環境変数の追加)
環境変数を追加する。ユーザー名がpiの場合は以下の通り。参考記事では.bashrcに環境変数を追加しているが、初心者には..bashrcが複雑なため、bash_profileを新規作成して、追加するほうがよい。
cd ~ touch .bash_profile vi .bash_profile PATH=/home/pi/.local/bin:$PATH source ~/.bash_profile
ここからはだいたい記事と同じでよい。フォルダ名が間違っており、正しくはumapではなくumap2である。
ポイント8(コマンドラインの取得)
ここからは試験のたびに実行する。
USB-UART変換ケーブルを持っている人は、それでラップトップPCとRaspberry Piとを接続すればよい。
持っていない人は、実行前にSSHを有効化する。Raspberry Pi ZeroにはUSBポートが実質1個しかない。スキャンしているときはUSBキーボードが使えなくなるためである。 左上のメニュー -> Preference -> Raspberry Pi Configuration -> Interfaces tab -> SSHを有効にする。 ラズパイへのssh接続を有効化する方法(2020年版) #RaspberryPi - Qiita
同じWi-Fiに接続している別のPCにおいて、Teratermを起動し、SSHで接続する。Raspberry piのIPアドレスはifconfigで調べる。user/passwordでログインする。
ポイント9(スキャン)
試験する機器をUSBポート(内側に)接続する。
facedancerを使用しないため、umap2を実行する前に、gadgetfsモジュールをロードする必要がある。
cd ~/umap/gadget pwd ls -l sudo sh start_gadgetfs_RaspiZeroW.sh cd ~/umap/data sudo umap2scan -P gadgetfs
- 参考として、Facedancerで実行するときは
umap2scan -P fd:/dev/ttyUSB0というコマンドをつかう。
離(以下は個人的なメモである。)
最新のRaspberry Pi OSではsudo apt -y install python-devによるpython2のインストールができない。代わりに以下の手順でpython2をインストールすることができる。ただし、この手順では、python-setuptoolsがインストールできないため、umap2のsetup.pyが実行できない。
こちらを参考に、まずpyenvを使えるようにする。 Raspberry PiにpyenvをインストールしてPythonをバージョン管理 #RaspberryPi - Qiita
必要なライブラリをインストールする。たまにupdateに失敗するので複数回行うとよい。
sudo apt update sudo apt upgrade sudo apt install -y git openssl libssl-dev libbz2-dev libreadline-dev libsqlite3-dev
pyenvをクローン。
git clone https://github.com/yyuu/pyenv.git ~/.pyenv
bashの設定をするため、.bash_profileを作成する。
cd ~ touch ./.bash_profile vi ./.bash_profile sudo vi ~/.bash_profile export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" source ~/.bash_profile
次のコマンドでpyenvのバージョンが表示されればインストールが完了している。
pyenv --version
2系の最新版をインストールする。
pyenv install 2.7.16
2系を標準に変更する。
python --version python3 --version pyenv global 2.7.16 python --version python3 --version
ちなみに、ここでsudoをつけてget-pip.pyを実行すると失敗する。
wget https://bootstrap.pypa.io/pip/2.7/get-pip.py python get-pip.py pip install requests pip install bitstring git clone https://github.com/BinyaminSharet/Mtp.git cd Mtp python setup.py install
ここまではできるが、次のpython-setuptoolsのインストールができずに手詰まりになる。
sudo apt install python-setuptools
エンジニアは、何も書かないのが正解
書き出し
お世話になっております。
ご返信ありがとうございます。
突然のメールで失礼いたします。御社のウェブサイトを拝見し、はじめてメールを差し上げます。
~様からの紹介でメールを差し上げます。
日頃から~をご利用いただき、誠にありがとうございます。
平素は格別のお引き立てをいただき、ありがとうございます。
ご無沙汰しております。
その後、いかがおすごしでしょうか。
立て続けのご連絡で失礼いたします。
書き終わり
どうぞ、よろしくお願い申し上げます。
何卒、よろしくお願い申し上げます。
引き続き、よろしくお願い申し上げます。
今後とも、お力添えのほど、よろしくお願い申し上げます。
勝手なお願いで大変恐縮ですが、よろしくお願い申し上げます。
ご確認のほどよろしくお願いいたします。
お手数ですが、ご返事いただければ幸いでございます。
ご検討のほど、よろしくお願い申し上げます。
お近くにお越しの際はぜひお立ち寄りください。
お会いできることを楽しみにしています。
ご参加をお待ちしております。
取り急ぎ、よろしくお願いいたします。
取り急ぎ、お礼申し上げます。
ご不明な際は、お気軽におっしゃってください。
今から楽しみでございます。
お礼を兼ねて失礼いたします。
なろう作品をKindleで読めるePub形式に変換
つかいかた
1. この記事の下部にあるRubyスクリプトを、任意のディレクトリに"narou2epub.rb"というファイル名で保存する。
2. 以下のようにコマンドラインで実行すると、epubファイルができる。[ncode]には、なろう作品のものを指定する。
$ ruby narou2epub.rb [ncode]
注記:未インストールのときは、事前に一度だけNokogiriとRubyzipのインストールが必要。
$ gem install nokogiri $ gem install rubyzip
3. できたファイルをメールに添付し、Kindle端末のメールアドレスに送信する。
Rubyスクリプト
require 'open-uri'
require 'nokogiri'
require 'zip'
def scrape(url)
opt = {}
opt['Cookie'] = "over18=no"
charset = nil
html = open(url, opt) do |io|
charset = io.charset
io.read
end
return Nokogiri::HTML.parse(html, nil, charset)
end
def replace(text)
text.gsub!(/<p id="L?\d+">/, '<p>')
text.gsub!('<br>', '<br />')
text.gsub!(/<img .*?>/, '(画像削除)')
text.gsub!(/<a .*?>/, '')
text.gsub!("</a>", '')
return text
end
def puts_novel(io, doc)
results = doc.xpath('//div[@id="novel_p"]')
unless results.length == 0
io.puts replace(results.inner_html)
io.puts '<hr />'
end
results = doc.xpath('//div[@id="novel_honbun"]')
io.puts replace(results.inner_html)
results = doc.xpath('//div[@id="novel_a"]')
unless results.length == 0
io.puts '<hr />'
io.puts replace(results.inner_html)
end
end
ncode = (ARGV[0].nil?) ? 'n6316bn' : ARGV[0]
doc = scrape('https://ncode.syosetu.com/'+ncode+'/')
title = doc.xpath('//p[@class="novel_title"]').inner_text
creator = doc.xpath('//div[@class="novel_writername"]').inner_text.gsub(/[\r\n]/,"").gsub(/作者:/,"")
chapters= []
episodes = []
results = doc.xpath('//dl[@class="novel_sublist2"]')
results.each do |r|
chapter_title = r.xpath('preceding-sibling::div[@class="chapter_title"][1]').inner_text
subtitle = r.xpath('descendant::a').inner_text
link = r.xpath('descendant::a/@href').inner_text
chapters.push chapter_title unless chapters.last == chapter_title
episodes.push [chapters.length-1, subtitle, link]
end
io = Zip::OutputStream.open(File.basename(title)+'.epub')
io.put_next_entry('mimetype')
io.puts 'application/epub+zip'
io.put_next_entry('META-INF/container.xml')
io.puts <<"EOS"
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="content.opf" media-type="application/oebps-package+xml" />
</rootfiles>
</container>
EOS
io.put_next_entry('content.opf')
io.puts <<"EOS"
<?xml version="1.0" encoding="UTF-8"?>
<package version="3.0" xmlns="http://www.idpf.org/2007/opf" xml:lang="ja">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>#{title}</dc:title>
<dc:creator>#{creator}</dc:creator>
<dc:language>ja</dc:language>
</metadata>
<manifest>
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />
<item id="title" href="title.xhtml" media-type="application/xhtml+xml" />
<item id="main" href="main.xhtml" media-type="application/xhtml+xml" />
<item id="style" href="style.css" media-type="text/css" />
</manifest>
<spine page-progression-direction="rtl">
<itemref idref="title" />
<itemref idref="toc" />
<itemref idref="main" />
</spine>
</package>
EOS
io.put_next_entry('toc.xhtml')
io.puts <<"EOS"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja">
<head>
<title>目次</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<nav epub:type="toc">
EOS
if chapters.length > 1
io.puts ' <h2>目次</h2>'
io.puts ' <ol>'
chapters.each_index {|i| io.puts ' <li><a href="main.xhtml#Ch_'+i.to_s+'">'+chapters[i]+'</a></li>'}
io.puts ' </ol>'
elsif episodes.length > 1
io.puts ' <h2>目次</h2>'
io.puts ' <ol>'
episodes.each {|e| io.puts ' <li><a href="main.xhtml#Ep'+e[2].gsub("/", "_")+'">'+e[1]+'</a></li>'}
io.puts ' </ol>'
else
io.puts ' <ol hidden="hidden">'
io.puts ' <li><a href="main.xhtml">本文</a></li>'
io.puts ' </ol>'
end
io.puts <<"EOS"
</nav>
</body>
</html>
EOS
io.put_next_entry('style.css')
io.puts <<"EOS"
html {
-webkit-writing-mode: vertical-rl;
-epub-writing-mode: vertical-rl;
writing-mode: vertical-rl;
}
nav ol { list-style: none; }
EOS
io.put_next_entry('title.xhtml')
io.puts <<"EOS"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja">
<head>
<title>表題</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h1>#{title}</h1>
#{creator}
</body>
</html>
EOS
io.put_next_entry('main.xhtml')
io.puts <<"EOS"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja">
<head>
<title>本文</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
EOS
if episodes.length == 0
io.puts '<section>'
puts_novel(io, doc)
io.puts '</section>'
else
temp= nil
episodes.each do |e|
STDERR.puts e.join(' ')
io.puts '<section>'
unless temp == e[0]
io.puts '<h2 id="Ch_'+e[0].to_s+'">'+chapters[e[0]]+'</h2>'
temp = e[0]
end
io.puts '<h3 id="Ep'+e[2].gsub('/', '_')+'">'+e[1]+'</h3>'
puts_novel(io, scrape('https://ncode.syosetu.com'+e[2]))
io.puts '</section>'
sleep 3
end
end
io.puts '</body>'
io.puts '</html>'
io.close
今後の課題
- 画像は表示できない。
キースイッチで、そろそろ動物園ができる。
動物園にしては、パンダが多めである。個人としては、Rhinoceros🦏が登場するのが待ちどおしい。
Linear
| - | Name | Actuation | Bottom-out |
|---|---|---|---|
| 🐬 | Durock Silent Linear Dolphin | - | 62g |
| 🦛 | Kinetic Labs Hippo | - | 63.5g |
| 🐕 | Kinetic Labs Husky | - | 63.5g |
| 🐼 | Invyr Panda | - | 67g |
| 🦙 | Alpaca | - | 62g |
| 🦙 | Silent Alpaca | - | 62g |
| 🪼 | Kailh Jellyfish "Y" | 50±10g | 60g |
| 🐸 | MOMOKA Frog | 54g | 62g |
| 🦩 | MOMOKA Flamingo | - | 67g |
| 🐍 | JWK Moyu Green Snake | - | 65g |
| 🐈 | Glorious Lynx | - | 60g |
| 🦦 | Gateron Baby Raccoon | 55±8g | - |
| 🦄 | Kailh Pink Unicorn | 50g | 63.5g |
| 🐅 | TTC Tiger | 45g | 55g |
Tactile
| - | Name | Actuation | Bottom-out |
|---|---|---|---|
| 🐼 | Drop + Invyr Holy Panda | - | 67g |
| 🐼 | Drop Holy Panda X | - | 60g |
| 🐼 | Glorious Panda | - | 67g |
| 🐼 | FEKER Like Holy Panda | 67g | - |
| 🐼 | TECSEE Purple Panda | 55g | 67g |
| 🐼 | Outemu Panda | 50g | 65g |
| 🐼 | TTC Holy Panda | 45±10g | 55±10g |
| 🐧 | Kinetic Labs Penguin | - | 63.5g |
| 🐟 | Kinetic Labs Salmon | - | 63.5g |
| 🦘 | Gateron Baby Kangaroo | - | 59g |
| 🦐 | Durock Shrimp Silent T1 | - | 67g |
| 🦈 | MOMOKA Shark | - | - |
| 🐝 | GamaKay Bumblebee | 45±10g | 65g |
| 🔥 | GamaKay Phoenix | 40±10g | 45g |
Clicky
| - | Name | Actuation | Bottom-out |
|---|---|---|---|
| 🦊 | Chosfox x Kailh Arctic Fox | 52g | 53g |
| 🪼 | Kailh Jellyfish "X" | 47±15g | - |
| 🦉 | Kailh White Owl | 46g | 70g |
キーボードのハードウェアマクロでExcelの文字を赤色にする
背景
Excelの利点は、マクロ/関数/入力制限/条件付き書式などの機能が利用できることです。これらは技術分野との相性がよいです。一方で、Wordと違って校閲機能がないため、変更点が可視化しにくいです。
Excelで変更点を記録するには、赤字や取り消し線で記録することになります。この操作はマウスを使うのが一般的ですが、キーボードから手が離れるため、作業効率が落ちます。
ショートカットキーもあるのですが、長いため、現実的ではありません。
| 赤字 | Alt,H,F,1,↓,↓,↓,↓,↓,↓,↓,←,←,←,←,ENTER |
| 黒字 | Alt,H,F,1,↓,←,←,←,←,ENTER |
ソフトウェアでマクロを登録するなどの対策も考えられますが、会社支給パソコンへの任意ソフトウェアのインストールは、禁止されています。
対策
Keychron Q2を購入し、ハードウェアマクロを登録することにしました。VIAではなく、Remapをつかっています。
会社支給パソコンは、キーボードが日本語配列であるため、選択肢が限られます。Keychron Q2は数少ない解決策といえます。