Androidのメモとか

ポキオの日記です。今日も遅延してない。

バチ抜けをNode-REDで地図に可視化する

つりはっく。

ポキオ Node-RED WorldMap バチ抜け

バチ抜けとは

釣り用語の一つです。

pokio-ringyo.hatenablog.com

pokio-ringyo.hatenablog.com

バチとはイソメやゴカイなどの環形動物のことを指し、バチ抜けとは春先の大潮の夜に海底に潜んでいるバチが一気に浮いてくる現象です。抜けたバチは種々の魚にとってはごちそうであって、バチを求めて魚が寄ってくるというわけです。バチ抜けは地域によって発生する時期がまちまちで、更に魚が寄ってくるかもわからない自然のイベントとなっているわけで、いつどのへんでバチ抜けが起こったのかを知りたいのは釣り人の性みたいなもの。

ポキオ輪業商会 横浜 メバリング シーバス プラッキング

今回は、Twitterで位置情報とともにつぶやかれている情報をもとに、バチ抜けなどの情報を地図上にマッピングしてみようと思います。

使うのはNode-RED

今回もNode-REDで実装していきます。フローはこんな感じ。

ポキオ Node-RED WorldMap バチ抜け

ポイントとなるノードはこちら。

Twitterノード

これでTwitter上の情報をほぼリアルタイムにスキャンして、指定したハッシュタグのツイートを引っ張ってきます。実際にツイートが見つかると、おそらくTwitter本家のTweet Objectの形式でデータが取得できます。

developer.twitter.com

ただ、仕様が複雑(笑)今回は、次のFunctionノードでテキトーに位置情報をパースします。

Functionノード

ここで、Twitterノードから取得したデータから位置情報をパースします。

if(msg.tweet && msg.tweet.place && msg.tweet.place.bounding_box && msg.tweet.place.bounding_box.coordinates){
    var longitude = 0;
    var latitude = 0;
    var array = msg.tweet.place.bounding_box.coordinates[0];
    var tweet = msg.payload.replace(/\r?\n/g,"");
    var link = 'https://twitter.com/' + msg.tweet.user.screen_name + '/status/' + msg.tweet.id_str;

    array.forEach(value => {
        longitude += value[0];
        latitude += value[1];
    });
    
    longitude /= array.length;
    latitude /= array.length;
    
    msg = {};
    msg.has_location = true;
    msg.payload = {
        "lat" : latitude,
        "lon" : longitude,
        "name" : tweet,
        "weblink" : link,
    };
}
return msg;

tweet.place.bounding_box.coordinates内の座標の平均値をとって、それを緯度経度として採用しています。後段の処理でCSVファイルに保存するため、Tweet本文は改行を削除してから保存しています。

CSVノード・Fileノード

前段の処理で、位置情報がパースできたTweetだけをCSVとして一旦保存しておきます。

WorldMapノード

今回の要。

flows.nodered.org

node-red-contrib-web-worldmapをインストールして、地図を表示できるようになります。フロー中にこのWorldMapノードがあると、特定のURLにアクセスすると地図が表示されるようになります。このノードに対して緯度経度などをくべると、地図上にピンが表示されるようになります。今回は、WorldMapにアクセスがあったときに発火するイベントを契機に、CSVを読み込み、そこに書かれた位置情報をすべてWorldMapにマッピングします。

これをつかって地図表示させてみました

こんな感じ。

ポキオ Node-RED WorldMap バチ抜け

まだまだTwitterの監視をし始めたばかりなのでピンはすくなめですが、ちゃんと可視化できています。

ちょっとずつピンが増えてくると楽しいですねぇ。

フローはこちら

[
    {
        "id": "add2a0f2.b28d2",
        "type": "twitter in",
        "z": "52e43921.021f08",
        "twitter": "",
        "tags": "バチ抜け,シーバス,#バチ抜け,#シーバス",
        "user": "false",
        "name": "バチ抜けツイート",
        "inputs": 0,
        "x": 130,
        "y": 300,
        "wires": [
            [
                "643d563.f2d9fa8",
                "1e92e179.29a17f"
            ]
        ]
    },
    {
        "id": "efe29703.79f498",
        "type": "comment",
        "z": "52e43921.021f08",
        "name": "ツイート監視",
        "info": "",
        "x": 110,
        "y": 260,
        "wires": []
    },
    {
        "id": "643d563.f2d9fa8",
        "type": "function",
        "z": "52e43921.021f08",
        "name": "位置情報パース",
        "func": "if(msg.tweet && msg.tweet.place && msg.tweet.place.bounding_box && msg.tweet.place.bounding_box.coordinates){\n    var longitude = 0;\n    var latitude = 0;\n    var array = msg.tweet.place.bounding_box.coordinates[0];\n    var tweet = msg.payload.replace(/\\r?\\n/g,\"\");\n    var link = 'https://twitter.com/' + msg.tweet.user.screen_name + '/status/' + msg.tweet.id_str;\n\n    array.forEach(value => {\n        longitude += value[0];\n        latitude += value[1];\n    });\n    \n    longitude /= array.length;\n    latitude /= array.length;\n    \n    msg = {};\n    msg.has_location = true;\n    msg.payload = {\n        \"lat\" : latitude,\n        \"lon\" : longitude,\n        \"name\" : tweet,\n        \"weblink\" : link,\n    };\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 340,
        "y": 300,
        "wires": [
            [
                "8649b0d9.a5f98"
            ]
        ]
    },
    {
        "id": "dddeedd9.5a74b",
        "type": "worldmap",
        "z": "52e43921.021f08",
        "name": "",
        "lat": "",
        "lon": "",
        "zoom": "",
        "layer": "",
        "cluster": "",
        "maxage": "",
        "usermenu": "show",
        "layers": "show",
        "panit": "false",
        "panlock": "false",
        "zoomlock": "false",
        "hiderightclick": "false",
        "coords": "none",
        "showgrid": "false",
        "allowFileDrop": "false",
        "path": "/worldmap",
        "x": 1060,
        "y": 460,
        "wires": []
    },
    {
        "id": "8649b0d9.a5f98",
        "type": "switch",
        "z": "52e43921.021f08",
        "name": "位置情報があるときだけ動作",
        "property": "has_location",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 580,
        "y": 300,
        "wires": [
            [
                "6d46063b.e4bce8"
            ]
        ]
    },
    {
        "id": "433fb432.0e49fc",
        "type": "function",
        "z": "52e43921.021f08",
        "name": "ズーム",
        "func": "msg.payload = {\n    command : {\n        lat : msg.payload.lat,\n        lon : msg.payload.lon,\n        zoom : 6,\n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 930,
        "y": 480,
        "wires": [
            [
                "dddeedd9.5a74b"
            ]
        ]
    },
    {
        "id": "75d4e561.cf315c",
        "type": "worldmap in",
        "z": "52e43921.021f08",
        "name": "",
        "path": "/worldmap",
        "events": "all",
        "x": 100,
        "y": 460,
        "wires": [
            [
                "493cf6af.c9d1d8"
            ]
        ]
    },
    {
        "id": "493cf6af.c9d1d8",
        "type": "switch",
        "z": "52e43921.021f08",
        "name": "",
        "property": "payload.action",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "connected",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 250,
        "y": 460,
        "wires": [
            [
                "6381668a.b712c8"
            ]
        ]
    },
    {
        "id": "6d46063b.e4bce8",
        "type": "csv",
        "z": "52e43921.021f08",
        "name": "CSVに変換",
        "sep": ",",
        "hdrin": "",
        "hdrout": "",
        "multi": "one",
        "ret": "\\n",
        "temp": "lat,lon,name,weblink",
        "skip": "0",
        "strings": false,
        "x": 810,
        "y": 300,
        "wires": [
            [
                "96084250.d8c06",
                "23921cd8.841d44"
            ]
        ]
    },
    {
        "id": "96084250.d8c06",
        "type": "file",
        "z": "52e43921.021f08",
        "name": "history.csvに保存",
        "filename": "history.csv",
        "appendNewline": true,
        "createDir": false,
        "overwriteFile": "false",
        "encoding": "none",
        "x": 1010,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "23921cd8.841d44",
        "type": "debug",
        "z": "52e43921.021f08",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "x": 970,
        "y": 340,
        "wires": []
    },
    {
        "id": "3b137fe3.26fa",
        "type": "csv",
        "z": "52e43921.021f08",
        "name": "",
        "sep": ",",
        "hdrin": "",
        "hdrout": "",
        "multi": "one",
        "ret": "\\n",
        "temp": "lat,lon,name,weblink",
        "skip": "0",
        "strings": true,
        "x": 630,
        "y": 460,
        "wires": [
            [
                "5786ae6a.d8e1"
            ]
        ]
    },
    {
        "id": "6381668a.b712c8",
        "type": "file in",
        "z": "52e43921.021f08",
        "name": "history.csvを読み込み",
        "filename": "history.csv",
        "format": "utf8",
        "chunk": false,
        "sendError": false,
        "encoding": "none",
        "x": 440,
        "y": 460,
        "wires": [
            [
                "3b137fe3.26fa"
            ]
        ]
    },
    {
        "id": "5786ae6a.d8e1",
        "type": "function",
        "z": "52e43921.021f08",
        "name": "weblink作成",
        "func": "const weblink = {\n    \"name\" : \"Twitter\",\n    \"url\" : msg.payload.weblink,\n    \"target\":\"_new\"\n}\n\nmsg.payload.weblink = weblink;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 790,
        "y": 460,
        "wires": [
            [
                "dddeedd9.5a74b",
                "433fb432.0e49fc"
            ]
        ]
    },
    {
        "id": "cddaa0e7.caea7",
        "type": "comment",
        "z": "52e43921.021f08",
        "name": "Mapにアクセスがあったとき",
        "info": "",
        "x": 160,
        "y": 420,
        "wires": []
    },
    {
        "id": "1e92e179.29a17f",
        "type": "debug",
        "z": "52e43921.021f08",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "x": 310,
        "y": 340,
        "wires": []
    }
]

Node-REDをつかってGoogle Home Miniに時報を喋らせる

テレワークに便利。

ポキオ Google Home Mini 時報 Node-RED

tl;dr

  • Google Homeの「ルーティーン」を使ってもできる
  • 今回はNode-REDから時報を喋らせる
  • 定期的にタスクを実行して、喋らせたい文字列をGoogle Home MiniにCastするだけ

テレワークのタイムマネジメント

いうほどできてないんですが。ついうっかりミーティングの存在を忘れたりしがちなので、以前から1時間おきにGoogle Home Miniに時報を喋らせてました。

support.google.com

ルーティーンという機能をつかって、設定した時間に「OK, google. 今何時?」という問いを内部的に投げて、その答えをGoogle Home Miniから発話させることで、時報として活用していました。ただ、最近設定した時間の±1分くらいに発火する謎の病にかかってしまい、代替手段を探していました。で、今回はその方法としてNode-REDからGoogle Home Miniに対して時報を喋らせてみようと思います。

フローは至ってシンプル

最初にフローから。

ポキオ Google Home Mini 時報 Node-RED

こんな感じ。

ポキオ Google Home Mini 時報 Node-RED

まずはInjectionノードで定期実行をトリガー。「おいおい、真夜中に発火させてどうした?」みたいなこと思うかもしれませんが、Node-REDが動作しているRaspberry PiタイムゾーンがUKのままなので、運用でカバーしています・・・(笑)良い子は真似しないでね。

次のFunctionノードで発話させるメッセージを作成しています。こちらでもタイムゾーンをゴニョゴニョしてます。

const date = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));
msg.message = "現在の時刻は、だいたい" + date.getHours() + "時です。";
return msg;

そして、実際の発話はnode-red-contrib-castノードで実行しています。

flows.nodered.org

文字通り、Castを行うノードですが、対象のIPアドレスGoogle Home MiniのIPアドレスにすると、msg.messageのStringを発話してくれます。

実際のフローはこちら

[
    {
        "id": "3e94927e.7c6d7e",
        "type": "cast-to-client",
        "z": "36b47ac9.340b66",
        "name": "",
        "url": "",
        "contentType": "",
        "message": "",
        "language": "ja",
        "ip": "192.168.1.31",
        "port": "",
        "volume": "50",
        "x": 630,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "fdae59a3.98caa8",
        "type": "inject",
        "z": "36b47ac9.340b66",
        "name": "22:00-00:00",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "0 22-23 * * 1,2,3,4,5",
        "once": false,
        "onceDelay": 0.1,
        "x": 140,
        "y": 60,
        "wires": [
            [
                "88c5f7d4.a36758",
                "b569a844.e50c58"
            ]
        ]
    },
    {
        "id": "88c5f7d4.a36758",
        "type": "debug",
        "z": "36b47ac9.340b66",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "x": 370,
        "y": 60,
        "wires": []
    },
    {
        "id": "b569a844.e50c58",
        "type": "function",
        "z": "36b47ac9.340b66",
        "name": "メッセージ作成",
        "func": "const date = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));\nmsg.message = \"現在の時刻は、だいたい\" + date.getHours() + \"時です。\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 380,
        "y": 120,
        "wires": [
            [
                "3e94927e.7c6d7e"
            ]
        ]
    },
    {
        "id": "a93920ed.ddbc3",
        "type": "inject",
        "z": "36b47ac9.340b66",
        "name": "00:00-10:00",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "0 0-9 * * 1,2,3,4,5",
        "once": false,
        "onceDelay": 0.1,
        "x": 140,
        "y": 120,
        "wires": [
            [
                "88c5f7d4.a36758",
                "b569a844.e50c58"
            ]
        ]
    }
]

カードが不正利用されました

流石にビビった。

ポキオ LINE PAY カード 不正利用 Amazon 500円

きっかけ

ある日、LINE PAYカード(JCBプリペイドカード)での利用通知がアプリに届いたんですが。

ポキオ LINE PAY カード 不正利用 Amazon 500円

「Amazon 500円」

いや、何も買った覚えはないし、Amazonは別のカードを紐付けているので、そもそもLINE PAYカードにAmazonから請求が来るのはおかしいと思い、とりあえず不正利用の可能性が高まりました。

まずはLINE PAYカードのカスタマーサービスに連絡

すると、こんな返信が。

【1】請求内容の確認について

恐れ入りますが、Amazonからの請求内容については、注文履歴をご確認ください。

なお250円、500円、4,900円の請求の場合、Amazonプライム会費の可能性があります。

ほうほう。少なくとも自分のアカウントを確認しても、請求先は違うので、これはなさそう。

【2】問題が解決できない場合

Amazonカスタマーサービスにご相談いただいた上で、覚えがないまたは、購入内容が確認できない場合は、弊社にて対象取引の取消手続きを承ることが可能です。​

弊社での取消手続きには、事前にお客さま自身で以下2点をご対応いただく必要があります。

・請求が発生したLINE Pay カードの解約操作

・最寄りの警察署へ被害内容の相談

どうやらLINE PAYカード側でトランザクションのキャンセルを行うには、いろいろ手順を踏まないとダメそうということがわかりました。結構面倒なんですね。

次にAmazonカスタマーサービスに連絡

出来事をAmazonカスタマーサービスに連絡したところ、その場で私のアカウントで発生した請求ではないと言うことが判明。引き続き、専門のチームで調査を進めた結果、翌日にAmazonから連絡があり、

  • 私以外のアカウントで利用されたことが確認できた
  • 私が承認した決済でないことから、そのアカウントに対して適正な対処をした(BANってこと?)
  • 500円に関しては返金予定

とのことでした。Amazonのフットワークの軽さに感動。

ということで、LINE PAYカードを解約することなく、返金が行われることになり、一旦解決。

なぜ不正利用されてしまったのか問題

起こってしまったことはしょうがないとして、なぜ不正利用されてしまったのかは気になるところ。

スキミングや物理的にカード番号・CVVが漏れた

多分ないはず。リアル店舗でLINE PAYカードを使うことはまずないし、カードを持ち歩こともないので、この線は可能性が低いと思っている。

ネットで利用した際にカード番号・CVVが漏れた

ここ数年の利用を遡ってみると、ほとんどがLINEアプリ上でのLINE PAYの決済で、JCBプリペイドカードとしての利用はあまりありませんでした。ただ、ちょっと怪しいのが、

  • 某自転車通販サイト(台湾)
  • 某総合ECサイト(中国)

このあたりは、Paypalが使えなかったため、LINE PAYカードで決済をしていました。あまり信じたくないですが、こういうところから漏れたんですかねぇ…。

総当り攻撃でカード番号・CVVが漏れた

そうでないと願っています。

不正利用されることは防げないという前提で対策をする

まぁ、犯人探しは想像の域を出ないので、今後はちゃんと対策をしていきたいです。

不要なカードは停止

クレジットカード・プリペイドカードは便利なんですが、それはセキュリティホールでもあるということがわかったので、使わないものは停止をしようと思います。LINE PAYカードなどは、アプリから一時的にカードを停止したりできるので、そういう機能も使っていこうと思います。

利用時に通知を受けられるようにする

今回も、問題発覚の要因になったのは利用通知でした。メインのカードも通知が飛ぶように設定をしました。

ポキオ LINE PAY カード 不正利用 Amazon 500円

www.smbc-card.com

カードの補償内容を確認

不正利用時にカード会社がどのくらい補償してくれるかは、会社によってまちまちです。LINE PAYカードの場合は、

(3) 1事故(一事由または同一原因による一連の事由により発生した損害をいいます。)あたりの補償限度額は、原則、10万円とします。ただし、前二号で定める補償対象となる損害の額が10万円を超過する場合は、利用者のご利用状況や警察当局による捜査結果等を踏まえ、補償限度額の引き上げを個別に検討します。

https://terms2.line.me/linepay_JP_Money_new_TermsofUse?lang=ja

となっており、10万円くらいは補償の対象っぽいです。カードによっては補償がなかったりすることもあるので、確認が必要ですね。

ヤバそうなサイトは捨てカードで決済

今回も、Paypalが使えない怪しげなサイトだったのでプリペイドカードで決済をしましたが、今後もそういうサイトを利用する際はメインのカードではなく、利用できる額が少額になっているプリペイドカードでの決済が安心かもしれませんね。

というわけで

不正利用なんて他人事だとおもってましたが、まさか自分の身にも降りかかると思ってもいなかったです。今後も気をつけて使っていきたい所存。

M5Stackに潮汐を表示させてみる

大潮でも釣れないときは釣れないんですけどね。

ポキオ M5Stack 潮汐

前回は潮汐情報のWebサービスを見つけました

全国各地のポイントの潮汐情報を日付を指定して取得できるすごい子でした。

relativelayout.hatenablog.com

今回は、これを使ってM5Stackに潮汐情報(とりあえず、大潮、中潮、・・・など)を表示させてみようと思います。

環境と実装

  • 実装環境:UIFlow(Betaのほう、v1.7.2)
  • バイス:M5Stack Basic

ここで、UIFlowはBetaのほうを使っているのがミソです。Betaの方だとNTPが使えるため、こちらを使用しています。(それに合わせて、M5Stackのファームウェアも更新しています)

ポキオ M5Stack 潮汐

こんな感じでブロックを組み立てていきます。やっていることとしては、

  • Wi-Fi接続
  • ntptimeの初期化
  • 定期的に潮汐情報ページにアクセス(日付指定にntptimeで取得した現在日付を使う)
  • 潮汐情報に応じて予め準備した画像を表示

このような処理です。

画像は予め準備

macKeynoteとかで、大潮用、中潮用、・・・の320x240な画像を予め準備して、M5Stackに流し込んでおきます。

ポキオ M5Stack 潮汐

ローディング画面用も一応作成。流し込んだ画像をコードから参照して表示させています。

早速動作確認

うーん、今日は中潮!ちゃんと動いてそうですね!

ポキオ M5Stack 潮汐

こういうガジェットを作ったからと言って、釣果が良くなるかは別の話なので、あしからず。

MicroPythonのコードはこちら

from m5stack import *
from m5ui import *
from uiflow import *
import wifiCfg
import ntptime
import urequests
import time

setScreenColor(0x222222)


url = None

wifiCfg.doConnect('*****', '*****')
image0 = M5Img(0, 0, "res/loading.jpg", True)




while not (wifiCfg.wlan_sta.isconnected()):
  pass
ntp = ntptime.client(host='jp.pool.ntp.org', timezone=9)
while True:
  image0.changeImg("res/loading.jpg")
  url = 'http://fishing-community.appspot.com/tidexml/index?portid=112&year=YYYY&month=MM&day=DD'
  url = url.replace('YYYY', str((ntp.year())))
  url = url.replace('MM', str((ntp.month())))
  url = url.replace('DD', str((ntp.day())))
  try:
    req = urequests.request(method='GET', url=url)
    if 0 < (req.text).count('大潮'):
      image0.changeImg("res/ooshio.jpg")
    if 0 < (req.text).count('中潮'):
      image0.changeImg("res/nakashio.jpg")
    if 0 < (req.text).count('小潮'):
      image0.changeImg("res/koshio.jpg")
    if 0 < (req.text).count('若潮'):
      image0.changeImg("res/wakashio.jpg")
    if 0 < (req.text).count('長潮'):
      image0.changeImg("res/nagashio.jpg")
  except:
    pass
  wait(3600)
  wait_ms(2)

macOS Big SurにESP8266開発環境を構築する(Arduino IDE)

今回もハマったお。

ポキオ ESP8266 Arduino macOS Big Sur

Big Sur環境に構築

Big Surをクリーンインストールしたのはいいのですが。

relativelayout.hatenablog.com

Arduino開発環境などが綺麗サッパリなくなってしまったので、いちから構築してみました。特に、よくプロトタイピングで使うESP8266が動くようにしてみました。

ESPr Developer(ピンソケット実装済)

ESPr Developer(ピンソケット実装済)

  • メディア: おもちゃ&ホビー

まずはArduino IDEをインストール

なにはともあれ。

ポキオ ESP8266 Arduino macOS Big Sur

www.arduino.cc

公式HPからmacOS向けのバイナリをダウンロード。そのままApplicationフォルダにどーん。

ポキオ ESP8266 Arduino macOS Big Sur

とりあえず起動はOKそうです。

ESP8266が開発できるようにする

Arduino core for ESP8266 WiFi chipを導入します。

github.com

設定画面のAdditional Boards Manager URLshttps://arduino.esp8266.com/stable/package_esp8266com_index.jsonを追加します。

ポキオ ESP8266 Arduino macOS Big Sur

するとBoard ManagerにESP8266が現れるので、インストールしちゃいます。

ポキオ ESP8266 Arduino macOS Big Sur

ESP8266をmacにつなぐと、認識もされるようです。

ポキオ ESP8266 Arduino macOS Big Sur

ビルドができない・・・?

と、ここまでは良かったんですが。いざコンパイルをしようとすると失敗してしまいます。

pyserial or esptool directories not found next to this upload.py tool.

An error occurred while uploading the sketch

どうやら、ライブラリをロードする部分で失敗しているようです。

mag.switch-science.com

forum.arduino.cc

~/Library/Arduino15/packages/esp8266/hardware/esp8266/2.7.4/tools/pyserial/serial/tools/list_ports_osx.pyの一部を書き換えます。

ポキオ ESP8266 Arduino macOS Big Sur

29行目・30行目を下記のように書き換えます。

iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')

これで書き込めるようになりました!

ポキオ ESP8266 Arduino macOS Big Sur

釣りをハックする

釣りです。

ポキオ 釣り IoT ハック

空前の釣りブーム

はい、最近釣りをしてばっかりです。

コロナ禍で勉強会が中止になったりオンライン開催でなんとなくテンションが上がらない一方で、地元で過ごすことが多くなったので自転車乗ったり釣りに行ったりしてたわけで。プライベートでコーディングすることからちょっと離れてしまったのは確かですが、新たな課題が見つかったのも事実です。

釣りはハックする余地がある?

自転車もそうですが、釣りも割と科学的な側面を持っていて、潮汐・水温・気圧などの気象条件や、キャスティングやアクションなどの釣り方、明暗や海水の濁り具合など、いろいろなファクターの重ね合わせで釣果が変わるといっても過言ではないと思っています。

ポキオ 釣り IoT ハック

一気に全部のファクターに手をつけちゃうと大変そうなので(笑)、まずはかんたんなところから手をつけていこうかと思います。

まずは潮汐

潮の満ち引きはもちろん重要なファクターの一つ、まずはここから着手します。潮汐データを取得できるWebAPIがあるかなと調べてみると、結構ありますねぇ。

fishing-community.appspot.com

www.data.jma.go.jp

大多数が有料なWebAPIのなかで、上記2つは無料でそこそこ使えそうな情報源になりそうでした。例えばfishstarのAPIだと、任意の日付・場所の潮汐情報が取得できます。

2021/01/21の横浜の根岸の潮汐データの場合、下記のようなURLにアクセスすると・・・

http://fishing-community.appspot.com/tidexml/index?portid=112&year=2021&month=01&day=21

<?xml version="1.0" encoding="UTF-8"?>
<tideinfo>
   <port-id>112</port-id>
   <port-name>根岸</port-name>
   <latitude1>35°24'</latitude1>
   <longitude1>139°38'</longitude1>
   <latitude2>35.400000</latitude2>
   <longitude2>139.633333</longitude2>
   <year>2021</year>
   <month>1</month>
   <day>21</day>
   <youbi></youbi>
   <sunrise-time>06:48</sunrise-time>
   <sunset-time>16:59</sunset-time>
   <moonrise-time>11:26</moonrise-time>
   <moonset-time>--:--</moonset-time>
   <tide-name>小潮</tide-name>
   <tidedetails>
      <tide-time>03:28</tide-time>
      <tide-level>74</tide-level>
   </tidedetails>
   <tidedetails>
      <tide-time>10:06</tide-time>
      <tide-level>148</tide-level>
   </tidedetails>
   <tidedetails>
      <tide-time>17:11</tide-time>
      <tide-level>66</tide-level>
   </tidedetails>
   <tidedetails>
      <tide-time>23:14</tide-time>
      <tide-level>111</tide-level>
   </tidedetails>
   <tidedetails>
      <tide-time />
      <tide-level />
   </tidedetails>
   <tidedetails>
      <tide-time />
      <tide-level />
   </tidedetails>
   <tidedetails>
      <tide-time />
      <tide-level />
   </tidedetails>
   <tidedetails>
      <tide-time />
      <tide-level />
   </tidedetails>
</tideinfo>

こんな感じでいろいろなデータが取得できました。これを使えば様々な用途に活用できそうですねぇ。

2020年のBest Buy(5つ)

今年もあんまり買ってないですね(真顔)

ポキオ BEST BUY 2020

はやいもので

もうすぐ2020年は終わり。色々あったけど、何もなかった2020年ですが、今年もBest Buyを決める季節ですね。今年は5つほど挙げさせていただきます。

PFU Happy Hacking Keyboard Lite2 for Mac 英語配列

ポキオ BEST BUY 2020

relativelayout.hatenablog.com

まずはキーボード。

Amazonのセールでやすかったので出来心で購入しちゃいました。打鍵音は非常に大きいので、Web会議中にカタカタやってるとたまに苦情が来ますが、押し心地は最高で、これに慣れてしまうと他のキーボードがもうだめです。そういう体になちゃいました。変な位置にあるバッククオートも、今となっては普通の位置。他のキーボードが変な位置にあるんだと、そう信じています。安心と信頼のUSB接続なので、(電波的に)過酷な環境下でもちゃんと打てるのも魅力のひとつ。

リングフィット アドベンチャー

ポキオ BEST BUY 2020

relativelayout.hatenablog.com

リングフィット アドベンチャー -Switch

リングフィット アドベンチャー -Switch

  • 発売日: 2019/10/18
  • メディア: Video Game

ギリギリ2020年じゃないけど、まぁいいや。7月までお世話になりました(あれ?w)。やっぱり、リングを押し込む運動だけでもかなり上半身が鍛えられた気分になれます。実際に、自転車に乗るとき、ダンシングがしやすくなりました(個人の感想です)。そろそろ再始動しようかしら。

ポンコタン ハイバックチェア

ポキオ BEST BUY 2020

relativelayout.hatenablog.com

何脚かテレワーク用に椅子を試してみましたが、結局これに落ち着きました。座面が低く、相対的にディスプレイが高めになって、長時間のデスクワークでも快適です。アウトドア用の椅子といっても侮ってはいけないわけで、包み込まれるような座り心地は素晴らしいです。これが壊れたら、Helinoxのハイバックを買ってみようかな。

SONY WF-1000XM3

ポキオ BEST BUY 2020

relativelayout.hatenablog.com

テレワークで全集中したいときの最強アイテム。たまにあった出勤時も電車の中で大活躍。耳から外すと曲が止まったり、もう一回つけると曲が再開したり、思いがけないおもてなしな仕様も最高です。

SONY PlayStation 5

ポキオ BEST BUY 2020

relativelayout.hatenablog.com

空気清浄機じゃありません。どちらかというと、空気が物理的に暖かくなるやつです。当選しちゃったので購入しましたが、やっぱり家にゲーム機があるといいですねぇ。シュッと短時間でNeed for Speedをやってリフレッシュ、みたいなことができますからね。今後のPS5のソフトにも期待しています。

というわけで

来年もよろしくおねがいしまーす。

「Androidのメモとか」は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。

このブログは個人的なメモ書きであったり、考えを書く場所であります。執筆者の所属する団体や企業のコメントや意向とは無関係であります。また、このブログは必ずしも正しいことが書かれているとは限らず、誤字脱字や意図せず誤った情報を載せる場合がありえます。それが原因で読者が不利益を被ったとしても、執筆者はいかなる責任も負いません。ありがとうございます。