raspberrypi」カテゴリーアーカイブ

Pico-Goが新しいVS Codeだと動かない模様

暫定対処

取り急ぎVS Codeを動くバージョンの1.65.2にダウン。Windows64はこちら 他はこちらみてバージョンを1.65.2にて適宜。

(2022/05/30追記)

また、こちらにあるようにForkされたPico-goをビルドしてもよい模様。

起きていたエラー

VS Codeの拡張機能エラーに下のが出ていた。

 → c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\node_modules\@serialport\bindings\lib\binding\node-v101-win32-x64\bindings.node
	at bindings (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\node_modules\bindings\bindings.js:126:9)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\node_modules\@serialport\bindings\lib\win32.js:1:98)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\node_modules\@serialport\bindings\lib\index.js:6:22)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\node_modules\serialport\lib\index.js:2:17)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\lib\connections\pyserial.js:20:19)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\lib\pymakr.js:18:17)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\lib\activator.js:28:15)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at h (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:699)
	at Object.<anonymous> (c:\Users\kitam\.vscode\extensions\chriswood.pico-go-1.4.3\pymakr.js:1:81)
	at Module.u._compile (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:4:1313)
	at Object.Module._extensions..js (node:internal/modules/cjs/loader:1216:10)
	at Module.load (node:internal/modules/cjs/loader:1035:32)
	at Module._load (node:internal/modules/cjs/loader:876:12)
	at Function.c._load (node:electron/js2c/asar_bundle:5:13331)
	at Function.i._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:32156)
	at Function.E._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:28749)
	at Function.g._load (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:99:61429)
	at Module.require (node:internal/modules/cjs/loader:1059:19)
	at require (node:internal/modules/cjs/helpers:102:18)
	at Function.r [as __$__nodeRequire] (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\loader.js:5:101)
	at u._loadCommonJSModule (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:104:30318)
	at u._doActivateExtension (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:90:13361)
	at u._activateExtension (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:90:12313)
	at processTicksAndRejections (node:internal/process/task_queues:96:5)
	at async g._activate (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:85:8154)
	at async g._waitForDepsThenActivate (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:85:8096)
	at async g._initialize (c:\Users\kitam\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:85:7460)

isuueとか見るとVS Codeが1.66になったときにNodeのバージョンも変わって不整合でテイルっぽい。

Raspberry PIのGPIO初期値

いま作っている殺菌ロボットがラズパイの電源を入れるとモーターが動き出していた。

使っているB+だとBCMで17&27,22&6にモータードライバー2つのFIN/RINをそれぞれつなげていたのだけど、GPIO6だけ初期値が1(IN)。

pi@raspberrypi:~$ gpio readall
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+

XORが1の時に動作をするのでGPIO22&GPIO6が1で動作していた。GPIOの初期値は(起動時にソフト的に書き換えられるけど)ハード的に決まっているもよう。トラブルで電源入れたら動き出すと気持ち悪いので初期値0のGPIOにつなげることにする。

古いRaspberry PIではVS Code remoteは使えない

“Unsupported architecture: armv6l”といわれる。使おうとした端末はRaspberry PI1のB+

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
[11:47:52.753] > ready: b428c5836ec1
[11:47:52.769] > Linux 4.19.66+ #1253 Thu Aug 15 11:37:30 BST 2019
[11:47:52.770] Platform: linux
[11:47:52.890] > b428c5836ec1: running
[11:47:52.950] > Unsupported architecture: armv6l
[11:47:52.952] > b428c5836ec1##27##
[11:47:52.952] Received install output: b428c5836ec1##27##
[11:47:52.953] Unsupported architecture
[11:47:52.953] Terminating local server
[11:47:52.956] Resolver error: The remote host's architecture is not supported
[11:47:52.963] ------

要望はあるけど使えないらしい。zeroでも使えないのね。

古いAndroidとラズパイで多機能モニターを作る

目的

以前作った店の呼び出しブザーは押されたらline&メール通知を飛ばすようになっている。ただし、押した側からみるとなんの反応も無いため動作しているか不安。なので、googleクラウドで合成させた音声を古いAndroidで流すようにした。(+画面でメッセージ表示)

なお、この記事を書いている段階ではエミュレータで動いたのみ。実機は古すぎて現行のAndroidStudioで素直にビルドできていない。ビルドする方法を調べてから再挑戦。

環境

呼出側

開発環境

  • OS Centos7
  • python 3.7.0
  • the Cloud Client Libraries for the Cloud Text-to-Speech API のアルファ版(ドキュメントが古いだけかも)

想定環境

  • raspberry pi typeB
  • python 3.7.0
  • the Cloud Client Libraries for the Cloud Text-to-Speech API のアルファ版(ドキュメントが古いだけかも)

触った限り、本番環境用のraspberry pi typeBで動作はするもののCloud Text-to-Speech APIの呼出が重い。10文字程度で3秒ほど。gRPCの接続/暗号化処理周りでcpuがいっぱいいっぱいになっている模様で端末能力の問題と思う。

呼び出される側(android)

開発環境

  • android studio 3.3.1
  • SHL22
  • Androidバージョン 4.2.2
  • compileSdkVersion/targetSdkVersion 28
  • minSdkVersion 16 (開発端末がこの時代の)
  • com.nanohttpd:nanohttpd-webserver:2.1.1

想定環境

  • IS11PT
  • Androidバージョン2.3.4

端末が古すぎるので開発環境を簡単に整えられなかったので、まずは新しめ(といっても8年ぐらい前)の端末で作成。

呼出側機能

概要

googleのText-to-Speech API を呼び出して、requestでAndroid内に起動させているnanohttpsにpostをするのみ。Text-to-Speech APIはほぼドキュメントどおり。requestのファイルのところはパラメータにMIMEタイプまでしていしてやらないとAndroid側で使っっているnanohttpdでファイルをよみ込めなかったので注意。

ソース

#!/usr/bin/env python3
# coding: utf-8
import requests

from google.cloud import texttospeech

def make_voice(current_time):
    _make_voice(current_time,"少々お待ちください")

def _make_voice(current_time,msg):
    file_name = 'output.mp3'
    client = texttospeech.TextToSpeechClient()
    synthesis_input = texttospeech.types.SynthesisInput(text=msg)

    voice = texttospeech.types.VoiceSelectionParams(
        language_code='ja-JP',
        ssml_gender=texttospeech.enums.SsmlVoiceGender.NEUTRAL)
    audio_config = texttospeech.types.AudioConfig(
        audio_encoding=texttospeech.enums.AudioEncoding.MP3)

    response = client.synthesize_speech(synthesis_input, voice, audio_config)

    with open('output.mp3', 'wb') as out:
        out.write(response.audio_content)

    url = 'http://127.0.0.1:8081/msg/'
    files = {'file': (file_name,open(file_name, 'rb'),'audio/mpeg')}
    payload = {"message" :  msg}
    r = requests.post(url ,files=files ,params=payload)
    print(r)
if __name__ == '__main__':
    make_voice( '2019/02/12 固定' )

呼び出される側

概要

内部で立ち上げているnanohttpdで情報(リクエスト)を受け取って、音声再生し画面にメッセージを出しておく。ちなみに、古いバージョンで動かそうとしてたので文法が古いものから新しいものまで混在しているので適宜読み替えてください。

package jp.co.epea.monitor;

import android.media.MediaPlayer;
import android.os.Handler;
import android.widget.TextView;


import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import fi.iki.elonen.NanoHTTPD;

import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND;
import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;

public class MoniterServer extends NanoHTTPD {
    private Handler handler;
    private TextView textView;

    public MoniterServer(Handler handler, TextView textView) {
        super(8080);
        this.handler = handler;
        this.textView = textView;

    }

    @Override
    public Response serve(IHTTPSession session) {
        Method method = session.getMethod();
        String uri = session.getUri();

        System.out.println(method + " '" + uri + "' ");
        if (uri.equals("/msg/")) {
            final String msg = session.getParms().get("message");


            Map<String, String> files = new HashMap<>();
            try {
                session.parseBody(files);
            } catch (IOException ioe) {
                return new NanoHTTPD.Response(NOT_FOUND, MIME_HTML, "none");
            } catch (ResponseException re) {
                return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
            }

            MediaPlayer player = new MediaPlayer();
            try {
                player.setDataSource(files.get("file"));
                player.prepare();
                player.start();
            } catch (IOException e) {
                e.printStackTrace();
            }

            handler.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText(msg);
                }
            });
            return new NanoHTTPD.Response(OK, MIME_HTML, "ok");
        } else if (uri.equals("/clear/")) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText("");
                }
            });
            return new NanoHTTPD.Response(OK, MIME_HTML, "ok");
        }
        return new NanoHTTPD.Response(NOT_FOUND, MIME_HTML, "none");
    }
}

いつもどおり関係ないけど

そういえば嫁のサイトをazureにも置きました

lambdaとRoute53を使ってDDNS機能を作った

概要

固定IPを振っていない店用にlambdaとRoute53を使ってDDNS機能を作った

背景

この間、店の呼び出しブザーを作ったけど死活監視をしていない。zabbixエージェントを入れようとしたけど店には固定IPが来ていないのでDDNSなり(VPNを貼るなり)しないといけない。

システム

環境

  • python3.7.0
  • raspbian9.4
  • Raspberry Pi B+
  • API Gateway
  • Lambda
  • Route53

システム概要

店のネットワーク内にあるラズパイからAPI Gateway経由でLambdaを呼出。

呼び出されたLambdaで呼び出し元(ラズパイ)のグローバルIPを取得。

今回のグローバルIPが前回のグローバルIPと違ったらLambda内でbotoを使ってRoute53のレコードを変更

lambdaのソース

ポン置きのラズパイから起動しているのでセキュリティ的に不安。なので、対象サーバとかzoneIdは引数でなくlambda側で持っている。
対象のAレコードなかったりしたら動かないけどログ見たらなんとかなるはず。

import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
        ZONE_ID = 'Route53のHosted Zone ID'
        logger.debug('call lambda')
        source_ip = event['source_ip']
        original_ip = event['original_ip']

        logger.debug( 'source_ip -> ' + source_ip)
        logger.debug( 'original_ip -> ' + original_ip)


        if original_ip == '' or original_ip != source_ip:
            logger.info('original_ip != source_ip')
            logger.info( 'source_ip -> ' + source_ip)
            logger.info( 'original_ip -> ' + original_ip)
            client = boto3.client('route53')

            try:
                response = client.list_resource_record_sets(HostedZoneId=ZONE_ID)
                target = [item for item in response['ResourceRecordSets'] if item['Name'] == 'hogehoge.epea.co.jp.' and item['Type'] == 'A'][0]
                logger.info(target)

                setting_ip = target['ResourceRecords'][0]['Value']

                if setting_ip != source_ip:
                    logger.info('modify start')
                    target['ResourceRecords'][0]['Value'] = source_ip

                    client.change_resource_record_sets(
                        HostedZoneId = ZONE_ID,
                        ChangeBatch = {
                            'Comment': '多分IPかわった',
                            'Changes': [{
                                'Action': 'UPSERT',
                                'ResourceRecordSet':target
                                }]
                            }
                    )
                    logger.info('modify finish')
            except Exception as e:
                logger.error('KOKODESUKOKODESU')
                import traceback
                traceback.print_exc()
                raise Exception("Check CloudWatch")
        return {
            'statusCode': 200,
            'body': source_ip
        }

全体の呼び出し元

Loopしながら呼び出し続けるのみ。

#!/usr/bin/env python3
# coding: utf-8
import json
import logging
import time
import os
import signal
import sys

import requests

def invoker(originalip):
    logger.debug('invocker start')
    logger.debug( 'original_ip -> ' + original_ip)

    headers = {'Content-Type' : 'application/json','x-api-key': ddns_token}
    payload = {'original_ip': original_ip}
    res = requests.post('https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/default/ddns'
        , data=json.dumps(payload)
        , headers=headers)
    if res.status_code != 200:
        print(res.text)
        print(res.status_code)
        raise Exception("TODO")

    logger.debug('res body ' + res.json()['body'])
    logger.debug('invocker finish')
    return res.json()['body']

def handler(signal, frame):
    logger.info('invocker stop')
    sys.exit(0)

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)

try:
    formatter = '%(levelname)s : %(asctime)s : %(message)s'
    logging.basicConfig(level = logging.INFO, filename = 'ddns.log', format=formatter)
except:
    print >> sys.stderr, 'error: could not open log file'
    sys.exit(1)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

ddns_token = os.environ['DDNS_TOKEN']
original_ip = ''
logger.info('invocker start')
logger.debug('ddns_token ->[' + ddns_token + ']')
while True:
    logger.debug('in main loop')
    original_ip = invoker(original_ip)
    time.sleep(900)

権限

ラムダ作った時に作られる権限の他にRoute53のレコード参照/操作権限を付与

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::change/hostedzone/Route53のHosted Zone ID",
                "arn:aws:route53:::hostedzone/Route53のHosted Zone ID"
            ]
        }
    ]
}

API Gatewayの設定

リクエストのマッピング

#set ($body = $util.parseJson($input.json('$')))
{
   "original_ip" : "$body.original_ip",
   "source_ip" : "$context.identity.sourceIp"    
}

エラー時のマッピング

正規表現(でなくそのままだけど) “Check CloudWatch”でメソッドレスポンスのステータスを500に指定

systemd

特にコメントなし

[Unit]
Description=DDNS Daemon

[Service]
EnvironmentFile=/home/pi/.config/environment.d/ddns.conf
WorkingDirectory=/home/pi/develop/ddns/
ExecStart=/home/pi/develop/ddns/invoke_ddns.py
ExecStop=/bin/kill ${MAINPID}
Restart=always
Type=simple
User=pi
Group=pi

[Install]
WantedBy=multi-user.target

それはそうとボルログに店の情報のせてもらったけど今はほとんど更新されてないのね。。

Raspberry Piで作った玄関の呼び出しブザーにLine通知も付けてみた

概要

このまえqiitaに書いたメール通知ができる玄関の呼び出しブザーにLine通知も付けてみたのでそのメモ。

環境

python3.7.0
raspbian9.4
Raspberry Pi B+

プログラム

事前準備

requestsモジュールを使うのでインストール

pip install requests

ソース

ほぼこちらのとおり。他に指定できるものはドキュメント参照。

LINE_TOKENは環境変数から取得。引数のcurrent_timeは呼び出し元でフォーマット済みの文字列

(line_notify.py)

#!/usr/bin/env python3
# coding: utf-8
import os

import requests

def line_notify(current_time):
    url = 'https://notify-api.line.me/api/notify'
    token = os.environ['LINE_TOKEN']
    headers = {"Authorization" : "Bearer "+ token}

    message =  '呼出ブザーが押されました %s' % current_time
    payload = {"message" :  message}

    r = requests.post(url ,headers = headers ,params=payload)

if __name__ == '__main__':
    line_notify( '2018/11/12 固定' )

他のソース/設定ファイル

コントローラー

(control.py)

前の記事ではwait_for_edgeでボタンプッシュを検知していたが、SIGINTきても処理がとまらないとのことでadd_event_detectに変更

また、systemdに登録したのでsignalをハンドラで処理するように変更

#!/usr/bin/env python3
# coding: utf-8
from datetime import datetime
from datetime import timedelta
import logging
import signal
import sys
import time

import RPi.GPIO as GPIO

from mail import visitmail
from line_notify import line_notify

def handler(signal, frame):
    logger.info('break')
    GPIO.cleanup()
    sys.exit(0)

def callBuzzer(channel):
    try:
        # ノイズ対策 静電気等のノイズでないか0.1秒以上続いていることをチェック
        time.sleep(0.1)
        if GPIO.input(pin) == GPIO.LOW:

            # 連続クリック対応 何度もメールが飛ぶと面倒なので一定時間内のボタンプッシュは無視
            # ノイズ対策の前に置くとこの処理時間で継続時間が長くなってしまうのでこの処理順にしている
            global before_calltime
            current_time = datetime.now()
            if current_time < (before_calltime + timedelta(seconds=60)):
                logger.debug('callback yet')
                return

            logger.debug('before_calltime:%s current_time:%s'
                % (before_calltime.strftime("%Y/%m/%d %H:%M:%S"),current_time.strftime("%Y/%m/%d %H:%M:%S")))
            before_calltime = current_time

            logger.info('visit actions call')
            str_current_time = current_time.strftime("%Y/%m/%d %H:%M:%S")
            visitmail(str_current_time)
            line_notify(str_current_time)
    except Exception as e:
        logger.error('error:  %s' % e)

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)

try:
    formatter = '%(levelname)s : %(asctime)s : %(message)s'
    logging.basicConfig(level = logging.INFO, filename = 'doorphone.log', format=formatter)
except:
    print >> sys.stderr, 'error: could not open log file'
    sys.exit(1)
logger = logging.getLogger(__name__)
#logger.setLevel(logging.DEBUG)
loop_logger = logging.getLogger('loop_logger')
#loop_logger.setLevel(logging.DEBUG)

logger.info('start doorphone moniter')
before_calltime = datetime.now() - timedelta(seconds=60)

GPIO.setmode(GPIO.BCM)
pin = 25

GPIO.setup(pin, GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(pin, GPIO.FALLING, callback=callBuzzer, bouncetime=300)

while True:
    loop_logger.debug('main loop runnning')
    time.sleep(1)

メール送信処理

(mail.py)

引数かえたぐらいで特に変更なし

#!/usr/bin/env python3
# coding: utf-8
import configparser
from email.mime.text import MIMEText
from smtplib import SMTP
import os

def visitmail(current_time):
    inifile = configparser.ConfigParser()
    inifile.read('./config.ini', 'UTF-8')

    ini_host = inifile.get('server', 'host')
    ini_port = inifile.get('server', 'port')
    ini_from = inifile.get('mail', 'from')
    ini_to = inifile.get('mail', 'to')
    ini_title = inifile.get('mail', 'title')

    with SMTP(host=ini_host, port=ini_port) as smtp:
        smtp.starttls()
        smtp.login(
                user = ini_from,
                password = os.environ['MAIL_PASSWORD'],
                )
        msg = MIMEText(current_time)
        msg['Subject'] = ini_title
        msg['To'] = ini_to
        msg['From'] = ini_from

        smtp.send_message(
                from_addr = ini_from,
                to_addrs = ini_to.split(','),
                msg = msg,
                )

if __name__ == '__main__':
    visitmail( '2018/11/12 固定' )

設定ファイル

(./config.ini)


[server]
host = mail.hoge.co.jp
port = 587

[mail]
from = kitamura@hoge.co.jp
to = kitamura@hoge.co.jp,keitaiaddr@ezweb.ne.jp
title = 呼出ブザーが押されました

systemd

(/etc/systemd/system/doorphone.service)

[Unit]
Description=doorphone Daemon

[Service]
EnvironmentFile=/home/pi/.config/environment.d/doormail.conf
WorkingDirectory=/home/pi/develop/doorphone
ExecStart=/home/pi/develop/doorphone/control.py
ExecStop=/bin/kill ${MAINPID}
Restart=always
Type=simple
User=pi
Group=pi

[Install]
WantedBy=multi-user.target

そういえば嫁のサイトをfirebaseに準備しました

Rasberry PIで電子工作

しばらく放置していますが、玄関ブザーにメール通知機能を付けたりしようと思いRasberry PIで電子工作をはじめました。

まだ、全然進んでいませんがハマリポイントをメモしときます。

ジャンパーワイヤー

始に、GPIOと繋げるジャンパーワイヤーがオス-メスなので都会に住んでいないと近場の店では売っていないことが多いです。これは素直にネットで買うと良いと思います。

 

きちんと、電気が通っているかの確認でテスターとかもあった方が良いと思いますが、壊れたおもちゃからはがしてきたモーターとかで最初は十分代用が效くと思います。
オモチャに付いていたLEDで代用しようとすると電流が多すぎたりで結構壊れることが多いです。どうしてもそれしかないという時は、直列にLEDつなげて、様子をみながら少しずつ減らしていけばなんとかなるかと。

pyenv

とりあえずpythonの資料が多いからpyenv入れとこうかと気軽にインストールしたくなります。ただし、マシンパワーが非力なのでインストールに時間がかかります。一晩以上はかかるので時間ある時にインストールしておくのがよいです。

pythonのバージョン

ライブラリ類は結構バージョン違いで動かないです。ネットからinputを受け取らない箇所はまずは素直にサンプルのバージョンで作った方が幸せになれる気がします。

開発マシン

Rasberry PIで直接開発をするとスペックが低いので色々辛いです。電源on/offに直結する以外の機能は別マシンで試してからRasberry PIに載せた方がいいかもしれません。

OSMCのインストールでちょっと手間取った

CentOS7で公式のOSMCサイトからyumレポジトリを取得しOSMCをインストール

インストーラー起動しようとしたら、なんか立ち上がらない

sudo view /var/log/messages

Jun 28 19:52:37 localhost osmcinstaller.desktop: sudo: 端末 (tty) が存在せず、パ
スワードを尋ねる (askpass) プログラムが指定されていません

普段遣いのユーザはパスワードなしsudoで運用しているのでだめっぽい。
パスワードなしで対処している例が多いけど、気持ち悪いのでsudoでインストーラーを起動。

sudo /usr/bin/osmcinstaller

ぺちぺち、画面に従ってUSBにインストールしようとしたけど、unmountできないとか言われてエラー終了しイメージができなかった。
普段遣いのユーザのGUIでている状態でUSBさしているので、権限があかんぽい。
通常のユーザでumountしたのちsudoでmountしたらOKだった。

Raspberry Piがfstabの編集ミスしたら起動しなくなった(他端末でマウントして復旧)

HDDの自動マウント設定をミスってしまったら、Raspberry Pi(Raspbian)がEmergency modeになって正常起動しなくなってしまった。
状況としてはこちらと同じ。

microSDの(windowsからもみれる)boot直下のcmdline.txtにinit=/bin/sh追記して起動しろというのを試したけど状況変わらず。

ファイル直接編集したらいいじゃんということでWindowsからいじろうとしたけど/etc/fstabのあるパーティションはext4。なのでWindowsからはいじれなかった。
Linuxマシンでマウントして編集できた。

osmcの自動ログイン

子供が動画を見るためにPCを取られてしまうので、余っていたraspberrypiを使ってテレビでユーチューブを見れるようにOSMCをインストールしました。

古いテレビなのでテレビリモコンではOSMCを操作できないのでアンドロイドにKoreを入れて操作。一応動きはするけれども、OSMCでログインしないとKoreがOSMCに繋がらないっぽい。ログインするためだけにraspberrypiにキーボードつなげるのは面倒。

というわけで、こちらをみて自動ログインを設定しました。

日本語画面だと、「設定」->「プロファイル」からユーザ画面?だったかを選ぶと自動ログインにするかどうかが選べました。複数ユーザを作っている場合は、動きが違うかも。

これで、再起動のたびにキーボードを触らないでもよくなったのでだいぶ便利。