日記とか、工作記録とか

自分に書けることを何でも書いてゆきます。作った物、買ったもの、コンピュータ系の話題が多くなるかもしれません。

Python + Seleniumで何度もログインするのを避けたい

Python + Selenium (Chromedriver)を使って、Webを巡回する仕組みを作ったりしています。
Webサイトにログイン、情報を読み取って保存、属性も併せて保存など、ひととおり自動化できて便利です。

しかしSeleniumは起動したその都度初期化され、状態は保存されません。
次の起動時にはログイン状態も初期化され、何度も繰り返しログインすることになるのです。
サイト側に不審なアクセスと判断されないかとか、心配になったりするわけです。

今回、TweetDeckで流れていくタイムラインの情報を保存、クラスタリングして遊べないかなと試行錯誤していたのですが、TweetDeckってログインするたびに「新しい環境からのログインがありました」とスマホに通知がくるので、心配になりました。その流れで、pickleを使うと簡単にcookieを保存しておくことができ、またそれをリストアすればログインの手順を省けるということがわかりました。

全体は長いので要所のみですが、こんな感じ。
Visual Studio CodeにJupyterを追加した環境で常時起動状態にしてタイムラインの情報を保存しています。

import pickle
from selenium import webdriver

ROOTDIR='D:/Python3.9/cookie/'
COOKIES='cookies.pkl'

# TweetDeckにログイン(一度getを呼んでから)
driver.get('https://tweetdeck.twitter.com/')
driver.set_window_size(2000, 1200)

# Cookieがあればロード、なければログイン
if os.path.exists(os.path.join(ROOTDIR, COOKIES)):
    cookies = pickle.load(open(os.path.join(ROOTDIR, COOKIES), 'rb'))
    for cookie in cookies:
        driver.add_cookie(cookie)

    # もう一度アクセスするとログイン状態になる
    driver.get('https://tweetdeck.twitter.com/')
else:
    # ここにログイン操作を書く
    ...
    # Cookieを保存しておく
    pickle.dump(driver.get_cookies(), open(os.path.join(ROOTDIR, COOKIES), 'wb'))

ドラゴンクエスト1(1986)

子供のころ、やりたくてもできなくて、そのまま大人になってしまった心残りってないでしょうか。
お菓子の箱買いとか。一日中ゲームで遊ぶとか。

ドラゴンクエスト1は、友達のうちで断片的に遊んだゲームでした。一度通しで遊んでみたかったんです。
何十年もたって心残りをひとつ消化しました。

f:id:WindVoice:20210704083048p:plain
MSXドラゴンクエスト1のエンディング

敵がラリホーを唱えると必ず眠ってしまうとか。
一人で冒険しているので眠らされるとそのまま死んでしまうこともあったりとか。
ランダムに偏りが感じられて一歩歩くごとに敵と遭遇したりとか。
LV感にギャップがあってメルキドに全然たどり着けなかったりとか。かげのきし強すぎ。
ベギラマをせっかくおぼえてもそのころの敵には効かなかったりとか。

理不尽仕様も散見されますが、レベルが上がるごとに着実に「強くなった! 強敵に勝てるようになった!」と実感できたりして、RPGを日本に紹介するきっかけとなったソフトとして、最初から最後までちゃんと遊ばせてくれる良作だと思います。インターネットのない時代でも、なんとか自分で手探りでクリアが可能です。

f:id:WindVoice:20210704084205p:plain
LV22のふっかつのじゅもん。もうりゅうおうに勝てる!

M5Stack + ローテーションサーボ FS90R

ローテーションサーボという部品があるのを知ったので秋月電子で買ってみました。

f:id:WindVoice:20190922090422j:plain

普通のサーボモーターは可動範囲が+90度~-90度などと決まっていますが、ローテーションサーボは可動域に制限がなくどちらの方向にも回り続けることができます。ただ、通常のサーボモーターと違って角度を決めて止まることはできません。動作としては、回る方向(時計回り、反時計回り)と速度をPWMを使って制御することになります。

データシートをみると、700~2300マイクロ秒のPulse Widthで操作するとあります。700~1500マイクロ秒で時計回り(1500で停止)、1500~2300マイクロ秒で反時計回りとなります。

ただこれ、何秒のうち、この時間をONにするのか、周期が良くわからないですね…… この資料はどう読んだらいいのでしょうか。0~700マイクロ秒を制御に使っていないことを考えると2300~3000も使っていない、つまり1周期は3000マイクロ秒と考えるのが自然ですか?

良くわからないので、実物で実験しました。プロの人からは怒られるかもしれませんがしかたないので。

ソースコード

Arduino IDEで書いています。結論から言うと、16bit、50Hzでポートを設定してある場合に、3000 - 5000、5000 - 7000のあたりで制御ができました。また、5000付近で停止ができるはずですが、実際にはどちらかにゆっくり回ってしまう状態になりがち(止まることもある)ので、停止をしたい場合にはledcWrite()で0を指定するほうが確実でした。

#include <M5Stack.h>

int FS90R = 16;

//PWMの設定
const double PWM_Hz = 50;     //PWM周波数
const uint8_t PWM_level = 16; //PWM 16bit(0~65535)

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Lcd.setBrightness(100);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE, BLACK);

  pinMode(FS90R, OUTPUT);
  
  //モータのPWMのチャンネル、周波数の設定
  ledcSetup((uint8_t)1, PWM_Hz, PWM_level);

  //モータのピンとチャンネルの設定
  ledcAttachPin(FS90R, 1);
}
void loop() {
  //[FS90R]
  //1500~ 700usecで時計回り(CW)
  //1500~2300usecで反時計回り(CCW)
  //1500(±45)usecのときに停止。とはいうものの良くわからない(Hz?)ので実機で確認。

  // 4950 - 5050 を停止として、3000 - 7000 で稼働するようだ。
  // 3000 - 5000 で 時計回り、
  // 5000 - 7000 で反時計回り。
  
  for (int i = 3000; i <= 7000; i+=50) {
    ledcWrite(1, i);
    delay(100);
    M5.Lcd.setCursor(0, 20);
    M5.Lcd.printf("PWM: %4d\n", i);
  }
  for (int i = 7000; i >= 3000; i-=50) {
    ledcWrite(1, i);
    delay(100);
    M5.Lcd.setCursor(0, 20);
    M5.Lcd.printf("PWM: %4d\n", i);
  }
}

動作の様子

いつものとおり、動画で動作の様子を残しておきます。ローテーションサーボがひとつだけであれば、外部電源はいりません。USBケーブルを外した状態でも動作可能でした。しかし、あまりスピードは出ませんね。4個つけて車にする計画でしたが、のんびり走るものになりそうです。

youtu.be

サーボモーター SG90を分解

サーボモーターSG90で以前作ったものから外そうとしたら外殻が壊れてしまったので分解してみようと思い立ちました。それだけですけど、分解って楽しいですよね。元に戻せないとしても!

f:id:WindVoice:20190919204123j:plain

底面の四角に小さいネジ頭があります。抜いてみたら長かった…… これを抜くと頭のカバーが外れます。

f:id:WindVoice:20190919204343j:plain

シールをはがすと底の部分が取れます。小さなモーターと基板が現れます。小さいモーター魅力的ですね。これだけ買えないかなぁ。

f:id:WindVoice:20190919204443j:plain

小さな基板にはICが両面実装されています。型番は読めない…… というか削り取られている?

f:id:WindVoice:20190919204631j:plain

基板の奥にはなにやら円盤状の部品が。私には役目がわからず。

f:id:WindVoice:20190919204641j:plain

小さい歯車も魅力的です。モーターの回転よりはだいぶ遅めに回るように見えます。

デジタル・マイクロサーボ SG90 (5個)

デジタル・マイクロサーボ SG90 (5個)

M5Stack + 光センサー(型番不明の硫化カドミウムセル)

なんだかちょっと調子に乗ってきたので、部屋に転がっていた、壊れた保安灯から取った硫化カドミウムセルの光センサー(たぶん)を使って明るさ検知をしてみました。身の回りのもので何ができるのか、ここしばらくそんなことばかり考えています。

光センサー

こんなやつです。見たことがある人も多いのではないでしょうか。表面のうねうねした回路に光が当たると、電気抵抗が変わるので電流を測れば明るさが間接的にわかるというものです。明るくなると抵抗が下がり、暗くなると上がります。安いのを買ったのですがすぐ壊れてしまい保安灯としては使えなくなりました。センサー部分を根元からニッパーで切って再利用する、というよくあるパターンですね。限りある資源を大切に。ちなみに購入しても100円未満のようです。

f:id:WindVoice:20190915202025j:plain

型番不明なのですが、調べてみたところ、硫化カドミウムを使ったものではないかと思われます。可視光線によく反応するので、人間が感じる明暗の感覚と近いデータがとれます。

回路

こんな感じで接続しました。5VとGNDの間を際限なく電流が流れないよう、抵抗を追加します。1kΩのものを使っています。その間に分岐を作って、アナログポート(ADC:Analog Digital Converter)につないで、入力モードで使います。M BUSの表をみると、ADCは35と36ですね。今回は35につないでいます。

f:id:WindVoice:20190915214138j:plain

できあがると、こんな感じになります。

f:id:WindVoice:20190915215009j:plain

コーディング

今回もArduino IDEで書いています。MicroPythonはまだ仕様が落ち着いていないのか、どうもドキュメント通りに動かないのが気になります。Cで書いたほうが仕様が調べやすいですね。アナログポートを読むときはanalogRead()を使います。整数値が取れるのであとは良いように使います。明るいほど大きい値が取れますが、実験したところ4095が上限のようです。

#include <M5Stack.h>

// 光センサーのポート番号。アナログ入力ポートは35が使えるそうだ
int CdS_Port = 35;

void setup() {
  Serial.begin(115200);
  m5.begin();

  // 雑音は決しておく。なんとなく。
  m5.Speaker.write(0);

  // 明るさ入力ポートの準備
  pinMode(CdS_Port, INPUT);
}

void loop() {

  int d;

  // 採取される値は 0 - 4095 のようだ
  d = analogRead(CdS_Port);

  // シリアルポートに報告
  Serial.printf("Read data -> %4d\n", d);

  // LCDに表示
  m5.Lcd.fillScreen(BLACK);
  m5.Lcd.setTextColor(WHITE);
  m5.Lcd.setTextSize(5);
  m5.Lcd.setCursor(0,0);
  m5.Lcd.printf("Read data -> %4d\n", d);
  
  delay(300);
}

結果

動作は簡単。結果も簡単。いくつかセンサーやモーターを使ってきたけれど、組み合わせで何をするかが問題ですね。基礎力は大事だけれど応用はどうしたらいいのやら……

youtu.be

M5Stack + サーボモーターSG90

以前Raspberry piで遊んだ時のサーボモーターSG90を、M5Stackとつないで動かしてみました。
実は全然うまくいかなくて試行錯誤して場当たり的に解決したのですが、まぁそのへんはそれはそれということで……

SG90

サーボモーターというのは、モーターの一種で、回転しつつ角度を決めて止まることができるものです。首振りのような動作ができるので、車のハンドル、ロボットの関節などで使われます。PWM(pulse width modulation)という信号を使って回転する位置を指定します。データシートにはこんな感じで書いてあります。

f:id:WindVoice:20190914233011p:plain

つまり、20ミリ秒(1/50秒)のうち、0.5ミリ秒だけGPIOをHIGHにしておけばサーボモーターは-90度の位置に固定され、2.4ミリ秒だけHIGHにすれば90度の位置に固定されるので、その間でほしい角度の分だけHIGHになるように調整してね、という意味です(たぶんあっているはず)。SG90はおよそ180度の範囲で動かすことができます。

Raspberry piのときにはモータードライバーのICを使って間接的に操作したのですが、M5Stackの場合はGPIOのポートを直接使えます。

今回はArduino IDEを使ってみていますので、ledcWrite()という関数を使えばいいようです。

配線

今回は、信号線としてGPIO 16を使ってみました。このへん、いろいろ試したのですが結局どれが適切なのかわからず…… あちこちつないでみたのですが画面が乱れたり音がなったりわけがわかりません。あとの2つはGroundと5Vにつなぎます。

f:id:WindVoice:20190914233758j:plain

USBケーブルでPCと接続しながら開発していたのですが、サーボを動かしているうちにM5Stackが再起動してしまい、GPIOの使い方が良くないのかとずいぶん悩んでしまいました。しかしよく考えるとUSBケーブルの電源品質が悪かったようで、電源不安定のため再起動していたようです。Arduino IDEからツール⇒シリアルポートモニタを開いてみていると、Brownoutエラーが記録されていました。

コーディング

試行錯誤の結果、SG90の仕様に合わせてPWMを50Hzに指定、指定する値の範囲は16bit(0~65535)にしてみました。この場合-90度(0.5ms)は1638、90度(2.4ms)は7864…… に相当する計算だと思うのですが、実機で試行錯誤したところ2300~9000くらいで首振りすることになりました。どうしてデータシートの計算と合わないのかなぁ。不思議ですが、こんなものですかね?

#include <M5Stack.h>

int led1 = 16;

//PWMの設定
const double PWM_Hz = 50;   //PWM周波数
const uint8_t PWM_level = 16; //PWM 16bit(0~65535)

void setup() {
  Serial.begin(115200);
  m5.begin();
  pinMode(led1, OUTPUT);
  //モータのPWMのチャンネル、周波数の設定
  ledcSetup((uint8_t)1, PWM_Hz, PWM_level);

  //モータのピンとチャンネルの設定
  ledcAttachPin(led1, 1);
}
void loop() {
  for (int i = 2300; i <= 9000; i=i+100) {
    ledcWrite(1, i);
    delay(30);
    Serial.printf("%d\n", i);
  }
  for (int i = 9000; i > 2300; i=i-100) {
    ledcWrite(1, i);
    delay(30);
    Serial.printf("%d\n", i);
  }
}

結果

動作結果は下のようになりました。片道2秒で右、左に首を振ります。それ以外には特段面白いこともなく申し訳ない。首振りで面白くなる何かを考えなければ。


M5stack + サーボモーターSG90

追記

MicroPythonでも書いてみたのですが、MicroPythonのPWMは1%刻みでしかコントロールできない様子。PWMのマニュアルではdutyは0~1023の間で指定することになっているのだけれど、100以上の値を指定するとエラーになるみたい。実際のdutyの定義域は0~100なのかな。サーボがカクついてしまいます。

from m5stack import *
import machine
import utime

p16   = machine.Pin(16)
pwm16 = machine.PWM(p16)

pwm16.freq(50)

while True:
    # dutyは 0.5/20=0.025 ~ 2.4/20=0.12 なので、これを100倍して3~12の間。
    # ……なのだけど、実機では4から15くらいで動作している。
    # MicroPythonではPWMは0~100%の1%刻みでしか操作できない?
    # サーボモーターの動作としては結構ぎくしゃくしてしまう感じだ。
    for d in range(4, 15):
        pwm16.duty(d) # dutyは0から100の間
        utime.sleep(1)
    for d in reversed(range(4, 15)):
        pwm16.duty(d) # dutyは0から100の間
        utime.sleep(1)

デジタル・マイクロサーボ SG90 (5個)

デジタル・マイクロサーボ SG90 (5個)