UPDATE: Kindle Weather Display

OpenWeatherMapのHPを見ていたらOne Call APIというのが新設されていて、現在、時間ごと、日付ごとの天気情報のデータを一度にダウンロードできるAPIがありました。今までは3時間ごとの天気情報をマニアックなやり方で日ごとの予測を弾き出していたのですが、このAPIを使えばシンプルなプログラムになります。この際なのでAPIをモジュール化して整理しました。ところが問題があって、すべてのローケーションでOne Call APIが使えるわけではなく、一部データが欠損するということがわかりました。まだ安定して使えないということです。それなので旧バージョンと新バージョンを分けることにしました。One Call APIは降雨予想があったので雲アイコンに載せてみました。

OpenWrt: mac addressの書き換え

数台のfon2405eのSPiフラッシュを載せ替えたまではいいのですが、一つ困った大きな問題があります。同じROMなのでmac addressが同じになってしまい、同時に起動していると通信障害が置きます。arpコマンドでHW addressを見てみればわかるように、同じmac addressだと衝突します。OpenWrtには/config/networkでmac addressのカスタム設定という方法があるのですが、なぜかfonには使えません。そこでフラッシュROMのEEPROM書き換えてmac addressを変えてみます。OpenWrtで予備知識を得られるのでまず目を通して置きます。

  • https://openwrt.org/docs/guide-user/installation/restore_art_partition
  • https://openwrt.org/docs/guide-developer/mac.address

これによると、rt305xの場合は+4なのでhexdumpで確認します。

hexdump -n 100 -C mtd2.bin
00000000  50 30 00 01 00 0c 43 30  50 88 ff ff ff ff ff ff  |P0....C0P.......|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
00000020  ff ff ff ff ff ff ff ff  00 0c 43 30 50 77 00 0c  |..........C0Pw..|
00000030  43 30 50 66 11 05 24 00  ff ff 2f 01 55 77 a8 aa  |C0Pf..$.../.Uw..|
00000040  8c 88 ff ff 0c 00 00 00  00 00 00 00 00 00 ff ff  |................|
00000050  ff ff 0d 0d 0d 0d 0c 0c  0c 0c 0c 0c 0c 0c 0c 0c  |................|
00000060  10 10 11 11                                       |....|
00000064

00 0c 43 30 50 88がralink SDKで作ったEEPROMのmac addressと一致しました。

そこで先頭のmac addressだけ変更することにします。

echo -n -e '\x01' | dd of=mtd2.bin bs=1 seek=4 conv=notrunc

hexdump -n 100 -C mtd2.bin
00000000  50 30 00 01 01 0c 43 30  50 88 ff ff ff ff ff ff  |P0....C0P.......|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
00000020  ff ff ff ff ff ff ff ff  00 0c 43 30 50 77 00 0c  |..........C0Pw..|
00000030  43 30 50 66 11 05 24 00  ff ff 2f 01 55 77 a8 aa  |C0Pf..$.../.Uw..|
00000040  8c 88 ff ff 0c 00 00 00  00 00 00 00 00 00 ff ff  |................|
00000050  ff ff 0d 0d 0d 0d 0c 0c  0c 0c 0c 0c 0c 0c 0c 0c  |................|
00000060  10 10 11 11                                       |....|
00000064

先頭が変わって01 0c 43 30 50 88になりました。これをmtdに書き込んで再起動します。

opkg install kmod-mtd-rw --force-depends
insmod /lib/modules/4.14.195/mtd-rw.ko i_want_a_brick=1
mtd -r write mtd2.bin factory

確かに変わってるのですが、期待したものと全く違っていました。原因は不明なもののmac addressの変更はこれでできました。

4: br-lan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 12:16:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff

Kindle: timelitのdark mode

Kindle weather displayとKindle news feed displayをダークモード対応にしたのでtimelitも対応させることにしました。自動的に切り替えは意外と落とし穴があって難しい。Kindle weatherは取り寄せるデータに日の出、日の入りがあるのでそのまま使えます。タイムゾーンに合わせてあるので問題は起こりません。ところがPythonモジュールで計算するとUTCを基準にしているので日付がずれます。これはなかなか厄介ですが、日付部分を取り除いて比較すればできます。当然、1日分違うため数分のずれが生じるので正確ではありませんがアプリケーションの精度からみて問題ありません。

timelitはもう少し複雑です。シェルでPythonを動かすのであまり複雑なスクリプトは組めません。さらにPython2なのでPython3より複雑になります。さらにpip等でモジュールをインストールできないので自前で用意しなければなりません。

色々調べると解決策がいくつかあり、特にモジュールをインストールしなくても比較的簡単に算出できる方法がありました。このスクリプトの問題は、日付を無視するので時間だけの情報になります。

Sun.py

import math
import datetime
import time

class Sun:

    def getSunriseTime( self, coords ):
        return self.calcSunTime( coords, True )

    def getSunsetTime( self, coords ):
        return self.calcSunTime( coords, False )

    def getCurrentUTC( self ):
        now = datetime.datetime.now()
        return [ now.day, now.month, now.year ]

    def calcSunTime( self, coords, isRiseTime, zenith = 90.8 ):

        # isRiseTime == False, returns sunsetTime

        day, month, year = self.getCurrentUTC()

        longitude = coords['longitude']
        latitude = coords['latitude']

        TO_RAD = math.pi/180

        #1. first calculate the day of the year
        N1 = math.floor(275 * month / 9)
        N2 = math.floor((month + 9) / 12)
        N3 = (1 + math.floor((year - 4 * math.floor(year / 4) + 2) / 3))
        N = N1 - (N2 * N3) + day - 30

        #2. convert the longitude to hour value and calculate an approximate time
        lngHour = longitude / 15

        if isRiseTime:
            t = N + ((6 - lngHour) / 24)
        else: #sunset
            t = N + ((18 - lngHour) / 24)

        #3. calculate the Sun's mean anomaly
        M = (0.9856 * t) - 3.289

        #4. calculate the Sun's true longitude
        L = M + (1.916 * math.sin(TO_RAD*M)) + (0.020 * math.sin(TO_RAD * 2 * M)) + 282.634
        L = self.forceRange( L, 360 ) #NOTE: L adjusted into the range [0,360)

        #5a. calculate the Sun's right ascension

        RA = (1/TO_RAD) * math.atan(0.91764 * math.tan(TO_RAD*L))
        RA = self.forceRange( RA, 360 ) #NOTE: RA adjusted into the range [0,360)

        #5b. right ascension value needs to be in the same quadrant as L
        Lquadrant  = (math.floor( L/90)) * 90
        RAquadrant = (math.floor(RA/90)) * 90
        RA = RA + (Lquadrant - RAquadrant)

        #5c. right ascension value needs to be converted into hours
        RA = RA / 15

        #6. calculate the Sun's declination
        sinDec = 0.39782 * math.sin(TO_RAD*L)
        cosDec = math.cos(math.asin(sinDec))

        #7a. calculate the Sun's local hour angle
        cosH = (math.cos(TO_RAD*zenith) - (sinDec * math.sin(TO_RAD*latitude))) / (cosDec * math.cos(TO_RAD*latitude))

        if cosH > 1:
            return {'status': False, 'msg': 'the sun never rises on this location (on the specified date)'}

        if cosH < -1:
            return {'status': False, 'msg': 'the sun never sets on this location (on the specified date)'}

        #7b. finish calculating H and convert into hours

        if isRiseTime:
            H = 360 - (1/TO_RAD) * math.acos(cosH)
        else: #setting
            H = (1/TO_RAD) * math.acos(cosH)

        H = H / 15

        #8. calculate local mean time of rising/setting
        T = H + RA - (0.06571 * t) - 6.622

        #9. adjust back to UTC
        UT = T - lngHour
        UT = self.forceRange( UT, 24) # UTC time in decimal format (e.g. 23.23)

        #10. Return
        hr = self.forceRange(int(UT), 24)
        min = round((UT - int(UT))*60,0)

        def convert_unix_time(datetime_obj):
            return int(time.mktime(datetime_obj.timetuple()))

        unixtime = convert_unix_time(datetime.datetime(int(year), int(month), int(day), int(hr), int(min), int(0)))

        return {
            'status': True,
            'decimal': UT,
            'hr': hr,
            'min': min,
            'unixtime': unixtime
        }

    def forceRange( self, v, max ):
        # force v to be >= 0 and < max
        if v < 0:
            return v + max
        elif v >= max:
            return v - max

        return v

timelit.sh.diff

--- timelit.sh.orig	2020-09-17 22:45:55.414873702 +0000
+++ timelit.sh	2020-09-17 22:45:37.886601266 +0000
@@ -1,11 +1,19 @@
 #!/bin/bash

+cd "$(dirname "$0")"
+
 # if the Kindle is not being used as clock, then just quit
 test -f /mnt/us/timelit/clockisticking || exit

+# my latitude and longitude
+lat=35.8034315
+lng=139.4275599
+offset=+9
+dark_mode='Auto'

 # find the current minute of the day
-MinuteOTheDay="$(env TZ=CEST date -R +"%H%M")";
+#MinuteOTheDay="$(env TZ=CEST date -R +"%H%M")";
+MinuteOTheDay="$(env TZ=GMT-9 date -R +"%H%M")";

 # check if there is at least one image for this minute
 lines="$(find /mnt/us/timelit/images/quote_$MinuteOTheDay* 2>/dev/null | wc -l)"
@@ -16,9 +24,40 @@
 	echo $lines' files found for '$MinuteOTheDay
 fi

+exec_dark_mode() {
+    t_sunrise=$(python -c "from Sun import Sun; coords = {'longitude' : $lng, 'latitude' : $lat }; sun = Sun(); print sun.getSunriseTime( coords )['unixtime']")
+    t_sunset=$(python -c "from Sun import Sun; coords = {'longitude' : $lng, 'latitude' : $lat }; sun = Sun(); print sun.getSunsetTime( coords )['unixtime']")
+    t_now=$(date '+%s')
+
+    [ "$t_sunrise" -gt "$t_sunset" ] && t_sunrise=$(( $t_sunrise - 86400 ))
+
+    t_sunrise=$(( ($t_sunrise + $offset * 3600) % 86400 ))
+    t_sunset=$(( ($t_sunset + $offset * 3600) % 86400 ))
+    t_now=$(( ($t_now + $offset * 3600) % 86400 ))
+
+    if [ "$t_now" -lt "$t_sunrise" ] || [ "$t_now" -gt "$t_sunset" ]; then
+        mode='dark_mode/'
+    else
+        mode=''
+    fi
+}
+
+case "$dark_mode" in
+    "Auto")
+        exec_dark_mode
+        ;;
+    "True")
+        mode='dark_mode/'
+        ;;
+    *)
+        mode=''
+        ;;
+esac
+

 # randomly pick a png file for that minute (since we have multiple for some minutes)
-ThisMinuteImage=$( find /mnt/us/timelit/images/quote_$MinuteOTheDay* 2>/dev/null | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" 1)
+#ThisMinuteImage=$( find /mnt/us/timelit/images/quote_$MinuteOTheDay* 2>/dev/null | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" 1)
+ThisMinuteImage=$( find /mnt/us/timelit/images/${mode}quote_$MinuteOTheDay* 2>/dev/null | python -c "import sys; import random; print ''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip()" 1)

 echo $ThisMinuteImage > /mnt/us/timelit/clockisticking

dark mode用の画像を作成します。

mkdir /mnt/us/timelit/images/dark_mode
cd /mnt/us/timelit/images/dark_mode

convert.sh

#!/bin/sh

ls ../*.png |while read f; do
    echo -n "- converting ${f#../}:"
    convert -size 600x800 -background white -depth 8 -negate "$f" "${f#../}"
    pngcrush -s -c 0 -ow "${f#../}"
    echo " result $?"
done

ソース

  • https://www.instructables.com/id/Literary-Clock-Made-From-E-reader/
  • https://stackoverflow.com/questions/19615350/calculate-sunrise-and-sunset-times-for-a-given-gps-coordinate-within-postgresql

dropbear client: pub-key authentication

今まではOpenssh Clientを使ったpub-keyのsshアクセスをやっていましたが、どうも漠然とした疑問があり、デフォルトインストールの dropbearでpub-keyアクセスはできないものかとずっと思っていました。というのはOpenWrtをインストールしたルーターは厳しい容量で運用しなければなりません。せっかくdropbeaarがあるのにOpensshクライアントを入れるのは無駄だと思われるからです。

色々と調べてみてどうやらできることがわかりました。

手順

OpenWrtは導入後に秘密鍵を持っています。それをpub-keyに変換します。

cd /etc/dropbear
dropbearkey -y -f dropbear_rsa_host_key | grep "^ssh-rsa " > dropbear_rsa_host_key.pub

秘密鍵をデフォルトローケーションに置きます。

mkdir /root/.ssh
cd /root/.ssh
ln -s /etc/dropbear/dropbear_rsa_host_key id_dropbear

pug-keyをリモート先のdropbearに登録します。

scp dropbear_rsa_host_key.pub root@<remote IP>:/tmp
ssh root@<remote IP>

[remote login]

cd /etc/dropbear
cat /tmp/dropbear_rsa_host_key.pub >> authorized_keys

秘密鍵を指定してログインする方法

ssh root@<remote IP> ~i /etc/dropbear/dropbear_rsa_host_key

buffalo wmr-300(トラベルルータ)のフラッシュROMを16MBに換装

fon2405eでSPIフラッシュROMを取り替えが成功したので、他のルーターもやってみました。取り寄せたSPIフラッシュメモリの在庫がかなりあるのでできれば16MBにすれば使い勝手がかなり向上します。8MBでも十分OpenWrtが使えるのですが、本格的に使おうとすると最低16MBは必要です。wmr-300はかなり小さいくトラベルルータとして持ち運びでき重宝するので、SPIフラッシュを取り替えることにしました。

今回はfon2405eの時のようにu-bootからファームウェアを焼かずに、フラッシュROM全体を移し替えます。下図のログは換装後の16MBフラッシュROMですが、”u-boot”、”u-boot-env”、 “factory” 、”firmware”をddコマンドで吸い出して、catコマンド繋ぎ合わせれば、アドレスの連続したファイルが出来上がり全体のフラッシュROMのコピーになります。”firmware”パーティションのアドレスが物理アドレスより大きくなっていますが、物理的に切られるので問題ありません。そのコピーを直接、フラッシュROMに書き込みます。書き込み後、ブートしてデバイスツリーでパーティションを拡大してビルドした新しいファームウェアにアップグレードします。

[    0.540941] m25p80 spi0.0: mx25l12805d (16384 Kbytes)
[    0.551116] 4 fixed-partitions partitions found on MTD device spi0.0
[    0.563789] Creating 4 MTD partitions on "spi0.0":
[    0.573341] 0x000000000000-0x000000030000 : "u-boot"
[    0.584223] 0x000000030000-0x000000040000 : "u-boot-env"
[    0.595739] 0x000000040000-0x000000050000 : "factory"
[    0.606752] 0x000000050000-0x000001000000 : "firmware"
[    0.620737] 2 uimage-fw partitions found on MTD device firmware
[    0.632600] Creating 2 MTD partitions on "firmware":
[    0.642510] 0x000000000000-0x00000017e04d : "kernel"
[    0.653323] 0x00000017e04d-0x000000fb0000 : "rootfs"
[    0.664142] mtd: device 5 (rootfs) set to be root filesystem
[    0.676980] 1 squashfs-split partitions found on MTD device rootfs
[    0.689368] 0x000000380000-0x000000fb0000 : "rootfs_data"

mt7620だと16MB以上のフラッシュROMを積めるのですが、問題はu-bootが対応していないとブートできないという問題があります。u-bootのソースはあるにはあるのですが、いざコンパイルして使うとなるとリスクが大きすぎます。今回は余っているフラッシュROMを使うことにしたので、16MBまでならオリジナルのu-bootで十分対応可能です。

OpenWrtはデバイスツリーをSPIフラッシュROMを16MB用に変えるだけで他には変更は加えていません。

リソース

UPDATE

バグはできるだけ潰したのですがどうしても一つだけ潰せないものがありました。デフォルトのネットワークの設定がどうしてもswitchの仕様になります。解決方法はfailesafeでsshログインしてmount_rootでrootfsにマウントし/overlay/upper/etc/config/networkを書き換えます。

config interface 'loopback'
	option ifname 'lo'
	option proto 'static'
	option ipaddr '127.0.0.1'
	option netmask '255.0.0.0'

config globals 'globals'
	option ula_prefix 'fd7a:751a:4b5e::/48'

config interface 'lan'
	option type 'bridge'
	option ifname 'eth0'
	option proto 'static'
	option ipaddr '192.168.211.1'
    option netmask '255.255.255.0'
	option ip6assign '60'

config device 'lan_eth0_dev'
	option name 'eth0'
	option macaddr 'xx:xx:xx:xx:xx:xx'

Kindle news feed

前回、Kindle weather displayが思った以上に良い出来でした。news feedのアイディアが浮かんだので作ってみることにしました。今回はsubprocessモジュールを使うことになったのですが、どうも使ってみると外部コマンドが終了しないうちから次のPythonスクリプトを実行してしまうらしく、外部コマンドの実行に失敗します。あまり良い方法ではありませんが、実行終了まで十分な時間を入れて問題を回避しました。このnews feedの肝は画像をベクター化したことです。ただのnews feedで文字列を表示しても面白くないので画像を入れてヴィジュアル化しました。

リソース