bokusui について

ソフトウェアハウスでのPG・SEから始まり、10年近く勤めた金融系企業の社内SEを数年前にやめ、フリーランス時代を経たのち法人成りしました。システム開発の全工程をこじんまりとやり続けています。

粒度問題

 何かシステムを作ろうとなったときによく感じるのが粒度問題です。

 簡単に言うと要件を出す立場の人側は、森を見て木を見ず、開発する立場の人側は木を見て森を見ず、そんな違う立場の間で起きる認識のずれ、コミュニケーションギャップ問題です。前者は対象を大きな範囲で捉えており、1本1本の木々には関心が無かったりします。後者は、個々の木を構成した結果の集合としての森なので、木々の方に着目する傾向があります。たまに木が大好きな前者もいたりはしますが、実は木でも「松」にしか関心が無かったり、、と関心の対象が異なるので話が噛み合わないのは当然です。しかも、当事者はそのような状況になかなか気がつけません。さらに開発工程で担当者が違うような体制の場合、さらに別の面で同じような問題になったりします。

 このような状態が続くと、当たり前ですが、最終的に良い結果にはならないので、早い段階で手を打ちたいですよね。でも、双方の意識を変えるという難しい問題なので、簡単な手はなかなか見つかりません。

 結局、言葉だけで伝えられない事は、一旦紙、ドキュメントという形に落とす意外無いように思います。まずは森である大枠をいろんな角度で対象を分類し、徐々徐々にドリルダウンしていきます。このフェーズの落とし所として意識するのは、そのドキュメントを見る人たちが意識している粒度の粗さと細かさのちょうど真ん中辺りの粒度レベルまでに抑える事です。話が進むにつれてドキュメントに結果をフィードバックし、仕様書的な物に変えていきます。

 人の意識を変えるのは難しいけど、少しずつ対話しながら双方が歩み寄るという、ここで言うところの粒度問題も、長年続く領土問題も平和に解決するには結局同じ方法しか無いないんだろうなあ。
 

SpringBootでZabbixAPIを使ってみる

 Zabbixの作業を自動化したいという事で、ZabbixAPIを使ってみました。対象のZabbixは3.2とちょっと古めのようですが、API自体はあまりバージョンは気にしなくてよさそうでした。ドキュメントが充実しているので助かります。

認証後、2件のホストを指定してホスト情報を取ってくるテストを書いてみました。

	@Test
	public void testZabbixAPI() throws Exception {
		String zabbixUser = "zabbixuser";
		String zabbixPass = "****";
		String zabbixEndpoint = "http://zabbixserver/zabbix/api_jsonrpc.php";

		//認証
		JSONObject json = new JSONObject();
		json.put("user", zabbixUser);
		json.put("password", zabbixPass);
		json.put("userData", "false");
		
		String token = null;
		ResponseEntity<String> response = Request("user.login", json, zabbixEndpoint, token);
		
	    JsonNode root = new ObjectMapper().readTree(response.getBody());
	    
	    token = root.get("result").get("sessionid").asText();

		//ホスト情報取得
		JSONObject target = new JSONObject();
	    String[] chkTarget = {"server01","server02"};
		JSONArray targetArray = new JSONArray(chkTarget);
	    
    	JSONObject param = new JSONObject();
		param.put("output", new JSONArray(Arrays.asList("hostid","host","name")));
		target.put("host", targetArray);
		param.put("filter", target);

		response = Request("host.get", param, zabbixEndpoint, token);
		ObjectMapper mapper = new ObjectMapper();
		ArrayNode arrayHost = (ArrayNode) mapper.readTree(response.getBody()).path("result");
		
	    System.out.println("hosts: " + arrayHost);
    }
	
	private ResponseEntity<String> Request(String method, JSONObject param, String endPoint, String token) throws Exception {
		JSONObject json = new JSONObject();

		json.put("jsonrpc", "2.0");
        json.put("method", method);
        json.put("params", param);
        json.put("id", 1);
		if(token != null){
            json.put("auth", token);
        }
        HttpMethod httpMethod = HttpMethod.POST;
        
	    HttpEntity <String> entity ;
		HttpHeaders headers = new HttpHeaders();
	    headers.add("Content-Type", "application/json-rpc");
	    headers.add("Accept-Language", "ja");
	    
	    if(param == null){
	    	entity = new HttpEntity <String>(headers);
	    }
	    else{
	    	entity = new HttpEntity <String>(json.toString(), headers);
	    }
	    RestTemplate restTemplate = new RestTemplate();
	    restTemplate.getMessageConverters()
        .add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
	    
	    ResponseEntity<String> response = restTemplate.exchange(endPoint, httpMethod, entity, String.class);
	    
		return response;
	}

で、こんな感じで戻ってきます。

hosts: [{“hostid”:”10387″,”host”:”server01″,”name”:”server01_DB”},{“hostid”:”10388″,”host”:”server02″,”name”:”server02_DB”}]

問題無く使えそうなので、要件を整理してから実装してみます。

プログラムは紙に書かない

 最近、地元の図書館で見かけてたまたま借りたのですが、「ペーパレス時代の紙の価値を知る」という本を読みました。

 認知科学?の実験結果報告部分が多く、途中かなり斜め読みになりましたが、紙の本を読むほうが読解が深い、手でページをめくる動作の優位性等、紙の本には電子書籍に対する優位性がまだあり、電子書籍だけでなくPC等含めた電子デバイスとは用途によって使い分けるのがよいだろう的な内容でした。

 確かに筆者も図書館にたまに行くくらいなので、紙の本は気持ち集中して読みやすいような気がしますし、Kindlewhitepaperを持ってはいるけど、最近は殆ど手を付けてなかったりなので、紙派といえばそうかもしれません。

 とはいえ、数千年ある紙の本の歴史をここ20年くらいしか経ていない電子書籍が超えるのは時期尚早なだけで、将来的には置き換わるとは思います。あと100年くらいたてばですが。

 最も、既に紙の本より電子メディアに慣れた世代が大きくなれば、もっと早い段階で置き換わる可能性もあるかもしれません。最終的には個々人の慣れの問題だと思います。主に紙の本と紙のノートをベースに学習してきた世代と、電子デバイス前提の世代間の優位性なんて、時間を掛ければ測れるのかどうかも怪しいものだし、重要なのは中身のコンテンツで、メディアそのものでもありません。教育の現場では過去の実績が乏しい方式に短時間でドラスティックな変革をするのは難しいですよね。でも、筆者のような昭和生まれ世代が受けてきた教育が、当時としても、まあ多少なりともベターだったんだろうとは思いますが、今の時代ならベターどころかただの時代遅れになっている可能性は十分あります。

 本の話になると一番の問題は、世界的に汎用的で共通した紙メディアを、1企業が開発した電子書籍が置き換えていくという所な気がします。ページ操作等含め、紙レベルで世界的に共通仕様の本のようなフォーマットが電子デバイスで実現できるかというと、、なさそうです。

 書くという要素については、例えばプログラムを今の時代に紙に書いている人は居ないでしょう。結局ファイルにならないと動きませんし。書いた後の活用、または一度書いたものの変更しやすさ等、こちらは既に大部分の作業が紙からPC等の電子デバイスに置き換わっているのが現状では無いでしょうか。

 ただ、マウスでウインドウを操作するような時間は結構無駄なので、特に2画面以上使ってる時にマウスで画面を跨いで行ったり来たりするのは、もうちょっとスムーズな操作は無いものでしょうか。

Webixでイベントを実装する

 今作っているWebのUIに、前回使ったw2uiをまた使おうかと思いましたが、今回対象のUIが他のWebサイトから遷移する前提なので、w2uiでは遷移元のWebサイト画面との違和感が強く、別のJavascriptUIフレームワークを探してみたら、Webixを発見。
 
 すっきりした見た目で違和感なさそうなので、今回はWebixを使ってみることにします。
 
 こちらのサイトに概要が書かれているのでとても参考になりました。

[blogcard url=”http://kumapanda.jp/?p=129″]

 で、UIの形は出来てきたので、イベントの実装をどうやるのかとドキュメントを見ていくと、ボタンのようなUI Controlでは、下記のようにidを指定してイベントを実装するようです。

webix.ready(function(){

   webix.i18n.setLocale("ja-JP");
   webix.ui({
      type: 'wide',
      cols: [
         { view: "button", id: "exec_button", label:"実行", width:300, align:"center"},
      ]
    });

   $$("exec_button").attachEvent("onItemClick", function(ids, some){
       webix.confirm({
          title:"",
          ok:"はい", 
          cancel:"いいえ",
          text: "実行してよろしいですか?"
       }).then(function(result){
          //OK時
       });
   });
});

ドキュメントが充実しているので、とても助かります。

SpringBoot ファイルアップロードでエラーが出る

 前にSpringBootで作ったWebアプリで、たまにファイルアップロードエラーが出ているとの事でログを確認。

 こんなログが吐かれています。

2020-08-03 16:31:28.826 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/xx].[dispatcherServlet] Servlet.service() for servlet [dispatcherServlet] in context with path [/xx] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.1401623156062740234.8080/work/Tomcat/xx/xx] is not valid] with root cause
java.io.IOException: The temporary upload location [/tmp/tomcat.1401623156062740234.8080/work/Tomcat/xx/xx] is not valid

 同事象のコチラを参考にテンポラリの場所を変えてみたのですが、開発端末のWindowsだと、どうも設定した値はデフォルトのテンポラリディレクトリの中に指定したディレクトリに出力しようとしてやっぱりエラーになるような感じでした。

 で、次にコチラを参考にspring.http.multipart.locationとして設定したところ、エラーは解消したようなのですが、非推奨になっています。。

 最後に実行環境のEC2にデプロイしたところ、spring.servlet.multipart.locationで指定してちゃんと動きました。アップロード後にtmpファイルは自動的に浄化されています。

 OSで違うなんて微妙な感じです。。

postgresで履歴テーブルから1時間毎の集計をSQLで取得する

 metabaseでpostgresデータを可視化するようにした所、履歴データが格納されているテーブルから1時間毎の集計を取りたいって事で、SQL1発で出来るんかいなと試行錯誤。

 今回の履歴テーブルはtimestamp型で開始時間と終了時間がセットされていて、誰が何をいつからいつまで実施していたかが記録されている、ような形になっています。これを1時間単位でその時間帯にどのくらい実施していたかをサマリたいという内容です。

具体的にはこんな感じでデータがあります。

selet * from xx_history ;

id, start_time, end_time, user_id, task_id
10, 2020-07-27 16:15:23, 2020-07-27 18:17:43, 432, 2
11, 2020-07-27 18:43:54, 2020-07-27 19:02:04, 154, 4
12, 2020-07-27 19:14:52, 2020-07-27 19:45:21, 432, 5
13, 2020-07-27 19:02:15, 2020-07-27 19:42:53, 154, 4

 これを下記のように1時間毎に時間を合計するような形で抽出したいという事になります。

時間範囲(XX時台), 合計時間
2020-07-27 16:00, 00:44:37
2020-07-27 17:00, 01:00:00
2020-07-27 18:00, 00:33:49
2020-07-27 19:00, 01:13:11

 履歴テーブルの開始時間と終了時間に対して、その時間帯が該当するのは、下記4つのパターンがあり、それぞれ時間の取り方が変わってくるのも面倒なトコロです。

・開始時間と終了時間がその時間範囲内
 → 開始時間から終了時間までの時間
・開始時間が時間範囲より過去だが、終了時間が時間範囲内
 → その時間範囲の開始時刻から終了時間までの時間
・開始時間が時間範囲内だが、終了時間は時間範囲より未来
 → 開始時間からその時間範囲の終了時刻までの時間
・開始時間は時間範囲より過去で、終了時間が時間範囲より未来
 → 1時間固定

 調べているとコチラを発見、postgresにはgenerate_seriesという便利なものがあるようです。

 generate_seriesで1時間毎のレコードを一定期間分生成出来るようなので、履歴テーブルとJoinしてSQLを作ります。

select time,
sum(case when xx_history.start_time >= time and xx_history.end_time <= time + interval '1 hours' then xx_history.end_time - xx_history.start_time
     when xx_history.start_time < time and xx_history.end_time >= time and xx_history.end_time <= time + interval '1 hours' then xx_history.end_time - time
     when xx_history.start_time >= time and xx_history.start_time <= time + interval '1 hours' and xx_history.end_time > time + interval '1 hours' then time + interval '1 hours' - xx_history.start_time
     when xx_history.start_time < time and xx_history.end_time > time + interval '1 hours' then time + interval '1 hours' - time
	 ELSE time - time END)
from generate_series( date_trunc('day', current_timestamp) - interval '2 days' , date_trunc('day', current_timestamp), '1 hours') as time
left join xx_history 
   on (xx_history.start_time >= time and xx_history.end_time <= time + interval '1 hours')
      or (xx_history.start_time < time and xx_history.end_time >= time and xx_history.end_time <= time + interval '1 hours')
      or (xx_history.start_time >= time and xx_history.start_time <= time + interval '1 hours' and xx_history.end_time > time + interval '1 hours')
      or (xx_history.start_time < time and xx_history.end_time > time + interval '1 hours')
group by time
order by time

 whereとCaseの条件は一緒なので、Whereの方はもっとコストをかけない条件に見直しした方がよいですね。
 実際はもっと複雑な形になるのですが、とりあえずSQL1発で出来そうな感じです。

「Twilio」で電話をかけてみた

自動で電話をかけたいな。
という話で、Twilioをトライアルしてみました。
要件としては架電して相手に日本語で要件を連絡、通話できたかを判定して・・・というところです。

Twilioにアカウントを作成し、ダッシュボードで電話番号を取得します。
DOCを参照しながら、とりあえず試すだけなので、Javaで適当に書いていきます。
基本的なところは、下記を参考にさせて頂きました。

[blogcard url=”https://qiita.com/mosin_nozomi/items/d30811022f1a19e620e0″]

SMSの発信や電話をかけるところまでは特に問題ありませんでしたが、上記サイトの内容とは現時点で変わっているのか、なかなか日本語を喋ってくれません。
あと、トライアル着信の「アカウントをアップグレードしてね」文句の後に何か押さなくてはいけないというのも暫くして気が付きました。。

どうしたら日本語を喋ってくれるのかと、ドキュメントを見ながらaliceとかを試してみたりしましたが、何かゴニョゴニョ言ってるけど何を言っているのか不明。。
結果的にダッシュボード左メニューのサービス→Programmable Voice→TwiML→テキスト音声変換のText-to-Speechという所に日本語はMizukiだと書いてあったので、やってみるとやっと喋ってくれました。ありがとうMizukiさん。

あと、TwiMLをインターネット上のURLからPOSTで取得できるようにしておく、という謎の制約もかなり厄介だったのですが、こちらも結果的に下記のようにすると必須では無かったようです。

import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Call;
import com.twilio.type.PhoneNumber;
import com.twilio.type.Twiml;

public class Example {
    public static final String ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
    public static final String AUTH_TOKEN = "your_auth_token";

    public static void main(String[] args) throws Exception {
        Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
        String callText = "お疲れ様です。了解頂けましたら、了解の旨を応答頂けますでしょうか?";
        String TwiMLstr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say language=\"ja-JP\" voice=\"Mizuki\">"
                + callText + "</Say><Pause length=\"10\"/><Say language=\"ja-JP\" voice=\"Mizuki\">それでは宜しくお願いします。</Say></Response>";
        Twiml TwiML = new Twiml(TwiMLstr);

        Call call = Call.creator(new PhoneNumber("+81登録した電話番号"), new PhoneNumber("+取得した電話番号"), TwiML).create();

        String id = call.getSid();
        for(int i = 0 ; i < 10 ; i++){
            Thread.sleep(1000*5);
            call = Call.fetcher(id).fetch();
            if(call.getStatus() == Call.Status.RINGING ||
                call.getStatus() == Call.Status.IN_PROGRESS ||
                call.getStatus() == Call.Status.QUEUED){
                continue;
            }
            else if(call.getStatus() == Call.Status.COMPLETED){
                //着信OK
                System.out.println("Call Completed " + call.getAnsweredBy());
            }
            else{
                break;
            }
        }
        System.out.println(call.toString());
    }
}

とりあえず要件は満たせそうです。
通話判定はもっとケースが必要ですし、判定待ちをもっとましな形にしたいところですが、トライアルなのでここまで。

ランニングコストがOKになって実際に導入すると決まったら、もうちょっと調べないとですね。

カテゴリー: Java

ラズパイ4でリレースイッチを使ってみた

 水遣り自動化を進めていたところ、作物がアブラ虫にヤラれてほぼ全滅。。
 ただ、今回は、コロナ禍最中に自宅で出来る事として、ビオトープを構築しメダカを飼いつつ水遣りを自動化するという、水遣り制御自体は元々ビオトープの子タスクでした。下記がiPadで描いた当初のラフデザインです。

 親タスクであるビオトープ構築に向け、プラ船、棚、ポリタンクを用意、プラ船にドリルで穴を開け、棚板にも穴を開けてポンプでポリタンクとプラ船を循環、ポンプの電源用にとソーラーパネルまで導入してしまいました。

 ソーラーパネルには、コントローラーが付属しており、バッテリーに充電しつつ、直流電源が使えるようになっています。バッテリーは車用でヘタって交換したブツが余っていたので、それをソーラーコントローラーに配線し、直流電源からポンプを稼働させます。

 ただ、このソーラーパネルはポンプを24時間付けっぱなしにしているとバッテリー充電量が足りず(コントローラーの消費電力も侮れない)、いつの間にかポンプが止まっているようなケースがありました。しかも、ポンプからの逆流対策をしていなかったので、プラ船からポリタンクへ逆流して水が無くなり、あわや、メダカ全滅!という危機に。
 これはいかんという事で、ポンプのホースに逆流弁を付け、暫くコントローラーに表示されるバッテリー残量(いまいち信用できないが)を見ながら動かしたり止めたりしてましたが、これこそ自動化すべき案件ですね。リレースイッチでポンプを定期的に動作させる事にしました。
 
 今後の水遣りも電磁弁を使用する予定なので、余裕を持って4チャンネルのコチラを購入。ラズパイのGPIOピンに接続するのは基盤に書かれているので簡単です。リレー自体に極性は無いようなので、動作させるポンプとコントローラーの直流電源に繋ぐ途中にリレーをかます形になります。リレーには接続口が3つありますが、基盤上で繋がっているように書かれた左側と中央を接続すると常時ONでスイッチするとOFF、右側と中央を接続すると常時OFFでスイッチするとONになるようです。

 

 単純にリレースイッチをON・OFFするPythonコードは下記のような感じです。

import RPi.GPIO as GPIO
import time

PIN = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN, GPIO.OUT, initial=GPIO.LOW)
# ON
GPIO.output(PIN, False)
time.sleep(60*8)
# OFF
GPIO.output(PIN, True)
GPIO.cleanup()

 せっかくなんで、既に記録をするようになっている照度センサーの値でスイッチを制御するようにしました。

 

 ホテイアオイが凄い勢いで水面を覆っていきますが、メダカも、コケ対策で導入したミナミヌマエビも子供が生まれていい感じになってきました。子供たちの何匹かはポリタンクに落ちてしまったので、頃合いを見て救出予定。

 でも、照度次第で動かすだけなら、ポンプとソーラーパネル直接繋げるでもよくない?

Windows環境からLinux環境にしたらJavaプロセスが動かない

 Windows上で動作させていた既存システムの開発環境を、今回Dockerで動かす形に変えていました。
既存システムの構成上、javaプロセスを2つ立ち上げる必要があり、これまでwindows上でバッチファイルで実行していたのを適当にシェルに置き換えて実行してみましたが、下記のようなメッセージが出てプロセスが動作しません。


See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details.
/var/lib/apl/tools/lib/app-XXX-server.jar: line 1: $'PK¥003¥004': command not found
/var/lib/apl/tools/lib/app-XXX-server.jar: line 2: $'¥373^¥226P': command not found
/var/lib/apl/tools/lib/app-XXX--server.jar: line 3: ??e?y: command not found
/var/lib/apl/tools/lib/app-XXX--server.jar: line 4: syntax error near unexpected token `)'
/var/lib/apl/tools/lib/app-XXX--server.jar: line 4: `?z?z?M)?^)? {,? ?MLy?x?P]?p?PK'

 なんじゃこりゃ?

JAVA実行コマンドとしては、
java -cp resource;lib/* jp.co.esoro.app-XXX.main
のような感じです。

 暫く試行錯誤していましたが、結果的にクラスパスを複数指定するところのセパレータ「;」はWindowsだけで、Linux環境では「:」じゃないとだめというオチでした。。

カテゴリー: Java

ラズパイ4で取ったセンサーデータをAthenaからMetabaseで可視化してみた

 前回の続きです。
 AWS IOTで、ラズパイで取得したセンサーのデータを定期的にPublishするようにしていましたが、実際のベランダに各センサーを配置してみました。
 いろいろ考えた結果、ベランダに電源を引いてラズパイを置くのでは無く、ラズパイは室内に配置、長いリボンコードをエアコンホースの口からベランダへ引くという形になりました。
 リボンコードの加工が面倒でしたが、これならエアコンホースのふたを加工したりしなくても一応大丈夫そうです。

 で、折角データを取得しているんですから、データを可視化してみたくなるものです。AWSにも色々BIサービスがあるようですが、趣味の範囲なので、コストを掛けず導入が容易そうなMetabaseでやってみます。

 MetabaseにAthena用のドライバーは標準では含まれていませんが、有難い事にこちらからJarを取得してプラグインのディレクトリに配置するだけで、簡単にAthenaにアクセス出来るようになりました。

 グラフになると入ってくる情報が違いますね。左軸は温度、湿度、右軸は照度、土壌水分です。温度・湿度センサーの値が取れずゼロになるのが回避できていないのが解ります。照度は明るいと数値が小さく、真っ暗で255になっています。また、湿度は夜高くなり、温度は朝の直射日光で40度になる時間帯があるようです。土壌水分は水やりの都度だいぶ数値的にはぶれるようで扱いが難しそうです。

 Metabaseは簡単に起動させられますが、メモリが1Gは無いと動かない模様、EC2等クラウドで動かすにはコストがネックになるので、結局、ラズパイ自身で動かす事になってしまいました。4Gメモリのラズパイ4なので早くは無いですが、十分動作します。

 こうなると、AWS IOT使っている意味無い気もしますが。。