hunamizawa’s blog

無い物は作りたい人のメモ帳

ESP8266/ESP32 でうるう秒を扱えるライブラリ『ESPPerfectTime』を作ったので使ってほしい

github.com

ESP8266/Arduino で置時計を作った。せっかくなので、うるう秒(午前8時59分60秒)を表示できるようにしたくなった。

SNTP とうるう秒

ESP8266/ESP32 で時計を作る利点として、SDK に SNTP クライアントが含まれているので、Arduino なら configTime(...) と1行書くだけで簡単に時刻合わせができる。時刻同期やその管理を気にしなくていいのは楽だ。

ところで NTP パケットには、うるう秒の挿入・削除を予告する Leap Indicator (LI) というフィールドが含まれている。残念ながら ESP8266/ESP32 組み込みの SNTP クライアントは、うるう秒に対応していない。なので自前で NTP パケットを解析して、LI の値を見ればいい、という話。

もう一つの問題:Round-Trip Delay

(ぶっちゃけ、時計を作るならこっちの方が大きな問題になる)

SNTP は、サーバーの現在時刻をネットワーク越しにクライアントに送ってもらうことで、時刻を合わせている。ということは、ネットワークの遅延が時計の精度にモロに影響してくるわけである。極端な例えをすると、RTT = 10秒の通信路なら、サーバーが送ったパケットがクライアントに届くまで平均5秒かかるので、何も考えなければクライアントの時計は5秒遅れてしまう*1

そこでクライアントは、リクエストを送る瞬間の時刻をパケットに記録しておいて、レスポンスが帰ってきた時に引き算で RTT を求めて、遅延を補正する処理をしている。詳しくは下の記事を見てほしい。

milestone-of-se.nesuke.com

で、手元で実験したところ、ESP8266/ESP32 組み込みの SNTP クライアントは、残念ながら RTT を補正する処理を実装していないようだ*2。ついでに、こちらも実装することにしよう。

実装

lwip に含まれている sntp.c を、うるう秒対応&RTT を補正するように改造した。

リクエストパケットを生成する initialize_request() の中で Transmit Timestamp を記録している。本来は UNIX time から NTP time に変換しなきゃいけないけど、RFC4330 の中で「サーバーは、クライアントから送られてきた Transmit Timestamp を、そのまま Originate Timestamp にコピーして送り返さねばならない」と規定されている*3ので、この変換をケチって UNIX time をそのまま記録している。

実際の RTT の計算は、レスポンスが帰ってきた後の process() で実行している。NTP のタイムスタンプは 1 bit = 1/(232) [sec] なので、上位 32 bit はそのまま秒として読めるし、下位 32 bit は 4295 で割るとマイクロ秒になる*4

使い方

README.md とか、Sample を見ればわかると思う。

gmtime() localtime() の使い方が2種類あることには注意してほしい。

// 現在時刻を取得する使い方
struct tm current = *pftime::localtime(nullptr);

// time_t の値を struct tm に変換する関数としての使い方
// time_t t;
struct tm tm = *pftime::localtime(t);

// 現在時刻を取得するのに、こういう使い方はしてほしくない
time_t t = time(nullptr);
struct tm current = *pftime::localtime(t);

欠点

世の中には、うるう秒が原因で障害を起こすシステムもある*5。そういうところ向けに、時計を少しずつ、何時間もかけてずらすことで、うるう秒の存在を消し去ってくれる(SLEW モード)NTP サーバーもある(例えば google のとか)。SLEW モードで運用されている NTP サーバーを同期先として設定していると、当然 LI = 0 しか帰ってこないので、「うるう秒を表示したい」という当初の目的は達成できない*6

NICT の NTP サーバー ntp.nict.jp のように、今まで STEP モード(うるう秒を正しく挿入する)で運用されてきた NTP サーバーでも、次回のうるう秒挿入時も STEP モードで運用してくれる保証はない。うるう秒の瞬間を確実に見たい人は、うるう秒挿入が近づいたら、サーバー管理者の告知をチェックするべきだろう。

*1:現実には、ESP8266 のデフォルトでは RTT >= 3秒でタイムアウトするので、1.5 秒以上遅れることはないが。

*2:まあ、普通の組み込み環境で、時刻の精度にそこまでこだわる必要は無いし……

*3:RFC から引用: If the server is unsynchronized or first coming up, ... the Transmit Timestamp field of the request is copied unchanged to the Originate Timestamp field of the reply. It is important that this field be copied intact, as an NTP or SNTP client uses it to avoid bogus messages. ... If the server is synchronized, ... The Originate Timestamp field is set as in the unsynchronized case above.

*4: (1/106) ÷ (1/232) = 4294.6...

*5:障害の原因になるのでうるう秒を廃止しよう、という議論があるくらいである。

*6:このような場合でも、デフォルトで1時間ごとに同期するので、時刻が大きくズレることはない。

【メモ】postfix + mailutils で Gmail 経由でメールを送る

忘れがちなので自分用にメモ。

apt-get install mailutils postfix

configure 画面になるので

  1. Satellite system を選択
  2. System mail name はそのまま
  3. SMTP relay host は [smtp.gmail.com]:587
  4. Root and postmaster mail recipient は空白
  5. Force synchronous updates on mail queue? は No
  6. Local networks はそのまま
  7. Mailbox size limit は 0 でもよいが、100MB = 104857600 bytes にしておく
  8. Local address extension character はそのまま
  9. Internet protocols to use は、IPv6 の設定がきちんとできているなら all そうでないなら ipv4

/etc/aliases をチェック

postmaster:    root

があればだいたい大丈夫

以下は https://qiita.com/hkato/items/e6e58abfc416d2820f4c も参照のこと

mail.cf を開いて以下を追記

smtp_use_tls=yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_tls_security_options = noanonymous

Gmail のユーザー名とパスワードの設定

# nano /etc/postfix/sasl_passwd
[smtp.gmail.com]:587 username:password

password はアプリパスワード

# chmod 600 /etc/postfix/sasl_passwd
# postmap hash:/etc/postfix/sasl_passwd
# newaliases
# systemctl reload postfix

送信テストしておkなら終わり。

logwatch をアップデートして Unmatched Entries を減らす方法

VPS 等のログ監視で定番の logwatch。仕組みとしては、事前に作成したperlスクリプトで、ログを1行ずつ仕分けして整形している。

ただ、監視対象のバージョンアップでログ出力書式が変化すると「パターンに一致しないログが見つかった」旨の警告が大量にレポートされて鬱陶しいことがある。例えばこんな感じ。

 --------------------- SSHD Begin ------------------------
(中略)
**Unmatched Entries**
Disconnected from user XXX 127.0.0.1 port 53084 : 1 time(s)
Disconnected from user XXX 192.168.XXX.XXX port 49867 : 1 time(s)
(同じような行が何行も続く)

これは、sshd の Disconnected ログの書式が変更された後に送られてきた、logwatch のレポートである。このログはユーザーがログアウトするたびに記録されるから、結果としてどうでもいいログがレポートにダラダラと並ぶわけである。これでは「Unmatched Entries って何やねん」と調べる時間がもったいないし、重要なログの見落としの可能性も高くなる。

logwatch の開発コミュニティでは当然すぐ修正されるが、それが各Linuxディストリの公式リポジトリに上がってくるまでにはかなりのラグが生じる。それまではゴミログにずっと耐えろって?

実は logwatch には、(systemd の drop-in ファイルのように)デフォルトのスクリプトを上書きすることなく変更する機能がある。以下は /usr/share/doc/logwatch/HOWTO-Customize-LogWatch.gz の抄訳である。

5. Customizing the Scripts

(4. で述べられている)設定ファイルのカスタマイズと同じように、(訳注: /usr/share/logwatch/scripts にある)既存のスクリプトもオーバーライドできます。

新しいスクリプトファイルは、/usr/share/logwatch/scripts から見て同じ相対パス、かつ同じファイル名にして、/etc/logwatch/scripts 以下に配置してください。このようなファイルが見つかれば、それに対応する /usr/share/logwatch/scripts 以下のスクリプトは無視されます。

つまり、最新のスクリプト開発リポジトリから丸々 /etc/logwatch/scripts にコピーしてしまえば、余計な苦労をせずにゴミログを減らせる=レポートが読みやすくなる。というわけで、シェルスクリプトを組んでみた。

#!/bin/bash -eu

if [ -e ~/logwatch-git ]; then
  cd ~/logwatch-git
  git pull origin master
else
  git clone -b master --depth 1 https://git.code.sf.net/p/logwatch/git ~/logwatch-git
  cd ~/logwatch-git
fi
cd scripts/services
cp * /etc/logwatch/scripts/services

実際にオーバーライドしているのは、/usr/share/logwatch/scripts 以下のみに留めている。

後は crontab に突っ込めば完了(自分は週に1度走らせている)。正常に cron が回っていれば

>From https://git.code.sf.net/p/logwatch/git
 * branch            master     -> FETCH_HEAD
Already up to date.

のようなメールが飛んでくるはずである。

当然のことだが、ディストリの公式リポジトリに比べると、新たなバグを持ち込んでしまったり、それが原因でログを見落としてしまうリスクは若干高い。皆さんも cron で回す時は「読みやすいレポートが得られるベネフィットの方がずっと大きい」と信じて回してください。

armbian で dotnet を走らせる(開発はしない)

armbian で dotnet を使いたいけど、Microsoft 公式リポジトリには x64 版しか無いのだった。無念。

でも幸いなことに、ARM32/64 用のビルド済みランタイムが用意されている。実機上でソースからビルドしなくても、とりあえず走らせることができる。

下のリンク先に、dotnet-install scripts なるものがあるのでダウンロード。armbian で開発……は現実的じゃないんで、SDK は省略しよう。

dotnet.microsoft.com

落としたら、root で次のコマンドを実行する。

mkdir /usr/local/bin/dotnet-runtime
./dotnet-install.sh --runtime dotnet --install-dir /usr/local/bin/dotnet-runtime
./dotnet-install.sh --channel Current --runtime dotnet --install-dir /usr/local/bin/dotnet-runtime
ln -s dotnet-runtime/dotnet dotnet

これで LTS 版(この記事を書いたときは 2.1)と、最新の安定版が手に入る。同じディレクトリに複数のバージョンをインストールして同居させるのが正しい使い方。

# dotnet --list-runtimes
Microsoft.NETCore.App 2.1.13 [/usr/local/bin/dotnet-runtime/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [/usr/local/bin/dotnet-runtime/shared/Microsoft.NETCore.App]

実は 3.x のランタイムでは 2.x のバイナリを実行できないので、使いたいバージョンは全てインストールする必要がある。必要なバージョンのランタイムが見つからないと、こんなふうに怒られる。後方互換性ないのか……

It was not possible to find any compatible framework version
The specified framework 'Microsoft.NETCore.App', version '2.1.0' was not found.
  - The following frameworks were found:
      3.0.0 at [/usr/local/bin/dotnet-runtime-3.0.0-linux-arm/shared/Microsoft.NETCore.App]

You can resolve the problem by installing the specified framework and/or SDK.

自己完結にすれば共有ランタイムは必要ないんだけど、ファイル数がめちゃくちゃ増えるので個人的に嫌い。

cron だって oom-killer に殺される

最近マイクラにドハマリして、1日12時間とかプレイしてしまうし、blog もサボってたので、マイクラを消して封印した。

前提条件

  • 自宅 NAS の構成: ODROID HC2 + Armbian 5.90 stable (Ubuntu Bionic with Armbian Linux 4.14.133-odroidxu4)
  • sysctl で vm.oom_kill_allocating_task = 1 している(アホみたいな量のメモリを要求してきたプロセスがいたら、他のプロセスではなくメモリを要求してきたプロセスに死んでもらう)

何があった

自宅 NAS から毎朝来るはずの logwatch のメールが来なかった。これまで原因不明のハングアップに何度か遭遇してきたので、「また落ちてんのか~」と思った。が、普通に SSH でログインできるし、samba も生きている。しかし node.js のプロセスが1つ Killed されていたので、oom-killer の仕業を疑った。

メールが来ないということは、postfix が死んだか?と思ったが大丈夫、メールもちゃんと飛んでくる。……ということは、logwatch が実行されていない?

logwatch は cron.daily によって実行されているので、まさかと思いながら systemctl status cron してみると

root@odroidhc2:/var/log# systemctl status cron
● cron.service - Regular background program processing daemon
   Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
   Active: failed (Result: signal) since Sat 2019-11-16 18:31:01 JST; 1 day 18h ago
     Docs: man:cron(8)
  Process: 712 ExecStart=/usr/sbin/cron -f $EXTRA_OPTS (code=killed, signal=KILL)
 Main PID: 712 (code=killed, signal=KILL)
   CGroup: /system.slice/cron.service
(中略)
Nov 16 18:31:01 odroidhc2 systemd[1]: cron.service: Main process exited, code=killed, status=9/KILL
Nov 16 18:31:01 odroidhc2 systemd[1]: cron.service: Failed with result 'signal'.

ここで /var/log/syslog を確認すると

Nov 16 18:31:01 localhost kernel: [107505.152146] cron invoked oom-killer: gfp_mask=0x14000c0(GFP_KERNEL), nodemask=(null),  order=2, oom_sc
ore_adj=0
(中略)
Nov 16 18:31:01 localhost kernel: [107505.152526] [ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name
Nov 16 18:31:01 localhost kernel: [107505.152586] [  712]     0   712     1700      514       7       0        0             0 cron
Nov 16 18:31:01 localhost kernel: [107505.152869] [14739]     0 14739     2049      516       7       0        0             0 cron
(中略)
Nov 16 18:31:01 localhost kernel: [107505.152887] Out of memory (oom_kill_allocating_task): Kill process 712 (cron) score 0 or sacrifice child
Nov 16 18:31:01 localhost kernel: [107505.152908] Killed process 14739 (cron) total-vm:8196kB, anon-rss:380kB, file-rss:1684kB, shmem-rss:0kB

マジか…… cron ぐらい重要なプロセスはデフォルトで oom_score_adj = -1000 になってると思ってたぜ……

対策

systemd には、Unit の status の変化をトリガーに、他の Unit を起動したり、poweroff / reboot を叩く機能がついている。これを利用して、万が一 cron が落ちてしまった場合に NAS 自体を再起動させてしまおう。

やり方は、systemctl edit cron するとエディタが開くので

[Unit]
FailureAction=reboot

と書いておく(大文字小文字の区別に注意)。こうすると、既存の conf ファイルを書き換えることなく、Unit に設定を追加したり上書きすることができる(ファイルの実体は/etc/systemd/system/cron.service.d/override.conf にある)。

  Drop-In: /etc/systemd/system/cron.service.d
           └override.conf

もちろん oom_score_adj を弄るのも有効な手段。

[Service]
OOMScoreAdjust=-1000

ただし注意点があって、cron によって起動される全ての子プロセスも同様に oom_score_adj = -1000 が設定されてしまう。したがって、crontab でメモリ食いのプロセスを起動させてしまうとカオスを招くかもしれない。なので筆者はこれを使わなかった。

ついでに同じ設定を postfix にもしておいた。何かあった時にメールが来ないのも結構困るからね。

参考記事

中華の格安Li-ionソーラー充電モジュール CN3791 その1

f:id:hunamizawa:20191016205435j:plain

ESP8266 を使って、デジタル百葉箱を作りたいな~と思っていて、どうやって電源を確保しようか検討中。

太陽電池+リチウムイオン二次電池で電源を賄う可能性を探るため、aliexpress を漁って見つけたのが、この充電モジュール(送料込380円)。

www.aliexpress.com

中国 Consonance Electronic(如韵电子)社の CN3791 という Li-ion 充電制御ICを使用したモジュールで、この値段で MPPT(最大電力点追従)制御もついているらしい。

で、CN3791 についてはネット上でも言及が少なく、日本語の情報は見当たらない。そこでデータシートを読みながら、このモジュールの動作について解析してみる。

データシートはこちら:http://www.consonance-elec.com/pdf/datasheet/DSE-CN3791.pdf

MPPTとは

MPPT(最大電力点追従)は、太陽電池の運転効率を最大限に引き出すための技術で、詳しい説明は各自ググるか、以下のリンクを参照してほしい。

MPPT方式 | 太陽光発電の仕組み

太陽電池MPPT制御 電圧制御MPPT回路の自作 - 日々の記録

2番目の記事の補足をすると、普通の降圧 DC/DC を太陽電池入力で使うと

  1. 雲が出て太陽電池の発電電力が低下する
  2. DC/DC の入力電圧が下がると出力電圧も下がる
  3. デューティ比を上げて(電圧が下がった分、代わりに電流をたくさん吸って)出力電力を保とうとする
  4. 入力電流が増加して太陽電池電圧がさらに下がる
  5. 3.に戻る
  6. 最終的にデューティ比が 100% になり、太陽電池電圧=蓄電池電圧になる

という負のスパイラルに陥ってしまうので、太陽電池を電源とする DC/DC では「入力電力が減少したら出力を絞る」制御、つまり MPPT が必要になる。

CN3791 の主な機能(データシートから抜粋)

  • 太陽電池入力対応、MPPT 制御
  • 降圧 DC/DC 方式
  • 充電終止電圧 4.2V±1%(過電圧保護付き)
  • 最大充電電流 4A(電流検出抵抗の値により変更可能)

モジュールの回路

回路構成はオーソドックスな 降圧 DC/DC。ほぼ typical application circuit と一緒だが、前段に回路が追加されている点が異なる。

f:id:hunamizawa:20191016170256p:plain

データシートより抜粋、赤字および青枠内は筆者追記

追加された回路

CN3791 | Tech Obsessed

上の記事の受け売りをすると、この部分は、太陽電池の逆接続からの保護と、バッテリーから太陽電池への逆流防止を兼ねている。Pch-MOSFET を用いた逆接続防止回路は定番で、解説は他に譲る。

ラジオペンチ Pch MOSFETによるハイサイド電源スイッチと逆接防止回路

パワーMOS FETで逆接続による回路焼損防止回路: エアーバリアブル ブログ

CN3791 の CHRG ピンはオープンドレイン出力で、充電中は Low (= GND) にプルダウンされ、D4 が点灯する。この時、FET のゲートも 20kΩ の抵抗を介して GND にプルダウンされ、FET が完全に導通する。

充電が完了すると CHRG ピンはハイインピーダンスになり、FET のゲートは 320kΩ の抵抗によりソース電圧にプルアップされ、FET がオフになる。この場合でも、順方向への電流はボディダイオードを介して流れるので、CN3791 は太陽電池から電力供給を受けられる。*1

「逆接続を防止する」ということは「逆向きの電流を通さない」、つまりバッテリーから太陽電池への逆流を防くことにもなる。このモジュールでは少しでも損失を減らすため D1 が省略されているので、この回路は必須となる。*2

 最大充電電流の設定

バッテリーの最大充電電流 I_{CH} は、電流検出抵抗 R_{CS} に依存し、次式で決定される。

{\displaystyle I_{CH} = \frac{120\,\mathrm{mV}}{I_{CS}}}

このモジュールでは 40mΩ が実装されているので、最大充電電流は 0.12V ÷ 0.040Ω = 3.0A となる。

太陽電池電圧の設定

MPPT の方式は「山登り法」などいろいろあるが、CN3791 で採用しているのはごく単純なものである。最大電力点 V_{MPPT} は周囲の環境(特に温度)により変化するが、そのへんは妥協して「V_{MPPT}は〇〇V」と決め打ちして、その電圧から大きく外れないようにデューティ比を制御するだけである。

具体的には、MPPT ピンの電圧が 1.205 V (Typ.)*3 付近になるように充電電流を増減させる。MPPT ピンには、太陽電池からの入力を抵抗 R3 と R4 で分圧して入れる。R3 と R4 の値は次式で決定する。

{\displaystyle V_{MPPT} = 1.205 \left( 1+\frac{\mathrm{R3}}{\mathrm{R4}} \right)}

このモジュールは R3 = 180kΩ、R4 = 20kΩ なので、V_{MPPT} = 12.05\,\mathrm{V}となる。R3 と R4 の分圧比を変えることで、6V や 9V の太陽電池用のモジュールを製造することができ、実際このモジュールは 9V 用と 12V 用がラインナップされている。

充電開始条件

以下の条件がすべて満たされると、CN3791 は充電動作を開始する。

  • 低電圧リセットが解除されている i.e. V_{CC} \gt V_{UVLO} \left(V_{UVLO}=3.8\,\mathrm{V\,typ.}\right)
  • スリープモードから復帰している i.e. V_{CC}-V_{BAT} \gt V_{SLPR} \left(V_{SLPR}=0.32\,\mathrm{V\,typ.}\right)
  • MPPT ピンの電圧が 1.23V 以上*4*5

充電動作

充電方式は、ごく普通の CC/CV である。V_{BAT} が充電終止電圧の 66.5% = 2.8V 以下の領域では予備充電動作に入り、充電電流が I_{CH} の 17.5% (Typ.) に制限される。

充電停止条件

CV 動作に入ったのち、充電電流が I_{CH} の 16% に減少した時点で充電完了と判断し、DONE ピンが Low に、CHRG ピンがハイインピーダンスになる。この状態からバッテリーが放電し、電圧が充電終止電圧の 95.5% ≒ 4.0V に低下すると、充電が再開される。

 

長くなったので、次回は実際の動作を観察してみたいと思う。

*1:損失は FET オンの時よりも大きくなるが、充電完了後の CN3791 自体の消費電流は 1mA くらいなので無視できる。

*2:D1 を省略するとバッテリーから CN3791 への逆流を止められなくなるが、データシートには「V_{BAT}=4.2\mathrm{V} のとき CN3791 の消費電流は 30μA に過ぎないので、これを無視できるなら D1 は省略可能」と記述されている。

*3:1.18V (Min.) - 1.23V (Max.)

*4:データシートをコピペしたが、MPPT ピン電圧の保証値が 1.23V (Max.) だからこう書いてあるのだろう。筆者が買ったモジュールでは1.2V くらいから充電された。

*5:この条件が偽でも、上2つの条件が満たされると CHRG ピンは Low になる。

VSCode + Remote WSL で Python を書く

ずっと Visual Studio 一本でコードを書いてきたけど、最近 VSCode の方も触るようになった。しばらく触ってなかった Python3 を書こうと思い立ち、環境を整えたのでメモ。

Remote WSL の使い方を知らなかった

最初、「Remote WSL って、統合ターミナルで Bash on WSL が開ける拡張かな~」と大きな勘違いをしていた。

code.visualstudio.com

上の記事を読んで分かったのだが、Remote WSL を使うと WSL 上に VSCode Server が立って、あたかも Linux 上で VSCode を使っているような環境を構築してくれるのだ。ビルドもデバッグも WSL 上でやって、その結果を Windows 側の GUI に表示してくれるという、便利極まりない機能だ。

今まで node.js も Python も、実際に動かすのは Linux だけど、開発のためだけに Windows にも環境を整える……ということをやっていた。二度手間になっていたし、Windows だとうまく動かないパッケージがあって開発を諦めることもあった。Remote WSL なら Linux 実機と同じフローで環境整備ができるし、実機にかなり近い環境でデバッグができる。

拡張機能のインストールは別々に

Windows 側の VSCode と、WSL 側の VSCode Server は別インスタンスになっているので、拡張機能をそれぞれにインストールする必要がある。でも WSL 側へのインストールも "Install on WSL" のボタンをクリックすれば済むので簡単。

今回は Python3 を書きたいので、WSL 側には Python Extension Pack さえ入れておけばおk。

pipenv

npm でいうところの package.json 的なものは何か?

調べると、Python のパッケージマネージャーって、なんかやたら種類がある。とりあえず pipenv を使うことにした。

初期化:

$ pipenv --python=/usr/bin/python3

この時、python インタプリタのパスを必ず明示的に指定する。pipenv --python 3 とかだと、Windows 側の python.exe を実行しようとして死んでしまう。

これで仮想環境が自動的に構築される(裏では virtualenv が走っている)。

パッケージのインストール:

$ pipenv install hogehoge

npm i hogehoge --save 相当。--dev オプションをつけると、npm i hogehoge --save-dev と同じ意味になる。

パッケージの依存関係は PipfilePipfile.lock に記録される。この2つのファイルの関係は npm でいう package.jsonpackage-lock.json と一緒。

復元:

$ pipenv sync

npm i --production に相当する。--dev をつければ npm i(引数なし)と一緒の意味。

qiita.com

仮想環境?

pipenv は virtualenv を使って仮想環境を作る。でも仮想環境って何なん?

python.keicode.com

この記事で大体把握した。npm なら node.js バージョン間の違いは小さいし、後方互換性が保たれてるから、たいして問題にはならないけど、Python だと 2.x と 3.x で文法が違うから、仮想環境に閉じ込めて管理するのがラク、ってことなのかな。

モジュールを認識しない?

Q: インストールしたはずのパッケージを、VSCode がいつまで経っても認識しなくて赤線が出っぱなし。ナンデ?

A: そのパッケージは virtualenv で作った箱庭(=仮想環境)に閉じ込められてるから、外から参照できない。VSCode をその仮想環境に入れる必要がある。

で、デフォルトでは VSCode が仮想環境を認識してくれないから、設定をいじる必要がある。詳しくはここを読んでほしい。

tekunabe.hatenablog.jp

pipenv + virtualenv では、仮想環境は ~/.local/share/virtualenvs/<名前> に生成されるようだ。したがって、

  • リモート [WSL] 設定の python.venvFolders.local/share/virtualenvs を追加
  • ワークスペース.vscode/settings.json"python.venvPath": ".local/share/virtualenvs" と書く

のどちらかの設定をしてから、画面左下の Python バージョンが書いてある所を押すと、作った仮想環境が出てくる。

これで IntelliSense も効くようになったし、快適に Python が書けそうだ。