lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

Raspberry Piでngrokのtcpアドレスをslackへ通知する

NAT越えしたい

自分の家に設置したラズパイにsshで入るならしかるべき方法で公開し、公開鍵設定やVPNを張るなどをして入れるとおもいますが、IoTセンサノードとして屋外で使う場合はモバイルルータなどで接続することが多いかもしれません。
モバイルルータが固定IPを持っていてNATトラバーサルできる契約のもありますが、そうでない時も多い。
できるものもオプション扱いでお金が別途かかる。もちろん常用するならIIJモバイルbiz+とかがいいと思いますが、そうでない時の方法です。

エージェントの選定

いろいろ探しました。いいなと思ったのが以下

Pulsewayはちょっとterminalの出来が悪すぎ…。というかそういう用途ではないですね。
Fluentdで端末のパラメータを監視したりするような作業をお手軽にできる感じがいいですね。

ngrokをNode.jsで使う準備

今回はngrokを使いました。超便利。ですが、起動のたびにアドレスとポートが変更されてしまいます。
アドレスを確認する手段の一つとしてSlackに自身のngrokアドレスを投稿させるコードを記述し、Systemdサービスとして登録します。

Node.jsを使います。ラズパイにサクサク入れていきます。
nohupはhupシグナルがngrokプロセスに送られないように使っています。
他にいい方法があれば教えてください。


sudo apt update
sudo apt install nohup
wget http://node-arm.herokuapp.com/node_latest_armhf.deb
sudo dpkg -i node_latest_armhf.deb

2020年2月4日追記:上記の方法ではできなくなりました。下記の方法に修正いたします。sudo npm cache cleanのところでエラーが出ても気にせず続けてしまいましょう。

sudo apt update
sudo apt install -y nodejs npm
sudo npm cache clean
sudo npm install npm n -g
sudo n latest

mkdir -p  ~/node_app/sendslack_ngrok
cd !$
nano package.json

ここでpackage.jsonを作っておきます。下記内容で構いません。

{
   "name": "sendslack_ngrok",
   "description": "sendslack_ngrok",
   "version": "1.2.3",
   "private": true,
   "scripts": {
   "start": "node app.js"
   }
}

保存します。

npm install ngrok node-slack os --save

Ngrokの設定

www.npmjs.com
ngrokは無料で十分使えますが、アカウントを取得しないとトークンがもらえません。(tcp使うのには必須)
サインアップはこちら
トークンはサインアップ後の画面の③で示されている部分です。赤ワクで囲んだ部分になります。
f:id:Lynx-EyED:20190410213929p:plain
コピーしておきます。
ラズパイのターミナルで実行しておく。

./node_modules/ngrok/bin/ngrok authtoken this-is-your-ngrok-auth-token

ホームディレクトリに.ngrok2/ngrok.ymlが生成されます。
あとで記述するthis-is-your-ngrok-auth-tokenの部分です。

Slackのアプリ設定

投稿したい専用チャネルを作るか自分へのDMとしたほうがいいでしょう(この記事の最後まで実施すると、起動するたびにSlackへ投稿します。#generalとかに投稿すると結構うざいです)。Slackアプリからは歯車のマーク -> アプリを追加する。Webからは自分のワークスペースにログイン後 https://{your-company-workspace}.slack.com/apps
で「incoming webhook」を検索。
f:id:Lynx-EyED:20190410203156p:plain
incoming-webhookアプリ設定画面に移行します。
「設定を追加」をクリック

f:id:Lynx-EyED:20190410203626p:plain
次画面の「チャンネルへの投稿」で投稿したいチャネルを選択または新規作成します。
登録後、生成された「Webhook URL」をコピーしておく。あとで記述するthis-is-your-slack-incoming-webhook-addressの部分です。

webhookコードの記述

さて、準備が整いました。コードを書いていきます
今回はapp.jsというファイルに記述します。

cd ~/node_app/sendslack_ngrok
nano app.js
const Slack = require('node-slack');
const ngrok = require('ngrok');
var slack = new Slack(' this-is-your-slack-incoming-webhook-address ');

ngrok_connect().then(url => {
    console.log('URL : ' + url);
    var message = 'tcp url: ' + url;
    slack.send({
            text: message,
            username: 'raspibot',
            icon_emoji: ":poop:"
        });
});

// ngrokを非同期で起動
async function ngrok_connect() {
    await ngrok.authtoken(' this-is-your-ngrok-auth-token '); 
    let url = await ngrok.connect({proto:'tcp',port:22}); // このコマンドを実行するのにトークンが必須
    return url;
}

いったんサービスとして登録する前に動作確認をします

cd ~/node_app/sendslack_ngrok
npm start

こんな感じでSlackに投稿できたら成功。
f:id:Lynx-EyED:20190410205756p:plain
うんこ絵文字が最初から入ってるなんて素敵。
何に使うのかしら。

サービスとして登録する

適当なフォルダでngroksend.serviceというファイルを作ります。
なお下記例ではホームディレクトリが/home/pi/になってます。

cd ~/
nano ngroksend.service

下記を記述します。

[Unit]
Description=ngrokSend
After=syslog.target

[Service]
Type=simple
WorkingDirectory=/home/pi/node_app/sendslack_ngrok/
ExecStart=/usr/bin/nohup npm start
TimeoutStopSec=5
StandardOutput=null
[Install]
WantedBy = multi-user.target

保存後、systemdに登録します。

sudo mv ngroksend.service /etc/systemd/system/
sudo systemctl start ngroksend.service
sudo systemctl enable ngroksend.service

動作が確認できたらsudo rebootしてください。

余談:ラズパイのケース

Raspberry Pi 3b+になってかなり発熱が心配になってきました。
動作させているプロセスにも依存しますが、2月のまだ寒い屋内で(室内気温12,3度)、ヒートシンクを貼ったファンレス状態では

vcgencmd measure_temp
temp=70.1'C

 
となっておりました。夏にお亡くなりになるのが怖く、ファン付きのものをいくつか試すことに。


同じプロセスを走らせつつ、このケースを使用して使ったところ
f:id:Lynx-EyED:20190410220246j:plain

vcgencmd measure_temp
temp=49.7'C

と、かなり冷却効果がありました。でもヒートシンクをCPUに貼っている場合剥がさないとファンが干渉します。
で、この上部に貼ってあるRaspberry Piアクリルステッカーのせいで笛吹き現象が起こってうるさかったので剥がしました。剥がしたら1,2度下がったけど誤差なのかは不明

  • GeeekPi Raspberry Pi3b+ Case

もう一つ。前者のより安価で、すこし背が高い。ヒートシンク付き。ケースに基板を嵌合してからヒートシンクを貼ったほうがいいでしょう。

前者のケースのファンより回転数が高めに思われますが、すごく静か。
1番目のものより個人的には気に入っています。でも高さがあるのでそこは運用方法に合わせて購入でしょうかねぇ。
f:id:Lynx-EyED:20190410220901j:plain

vcgencmd measure_temp
temp=42.0'C


冷却性能も良さげでした。




 

Swift PlaygroundsでのMVP実装を考えた

MVP実装考えなきゃいけないほど大規模開発しないよね

FatViewControllerガーって言うほど、iPad単体で大規模なプログラムは書かないとは思うのですが…

  1. 組み込みデバイスと通信するプログラムが多いので、ViewControllerが組み込みデバイスとの通信APIとごちゃごちゃになるのは必至
  2. 別のViewアーキテクチャの機器に移植するのが大変になる
  3. API変更があった場合の変更が辛くなる

MVCから逃げたいのはこのくらいかな。MVPにしたのはその他のアーキテクチャにくらべて学習コストが低そうと言うだけです。

MVPについてちょっとお勉強した

参考にしたサイト

そのまま、Model - View - Presenterで成り立っており、比較的それぞれが疎結合になっているアーキテクチャ
f:id:Lynx-EyED:20181018214726p:plain
Presenterの役割が重要で、

  • Viewからイベントを受け取り、Modelに処理を移譲
  • Modelから結果を受け取り、Viewに通知

したがってModel、Viewとも、互いに独立している。
今回はModelはNotificationCenterを使い、Presenter通知しています。
f:id:Lynx-EyED:20181018215939j:plain
iPad SwiftPlaygroundsですので、複数ファイルに記述するのが少々ハードルが高いので1ファイルに汚く書いてしまいました。

import PlaygroundSupport
import UIKit
import Foundation

// MVPの練習
// protocol宣言 --------------------
protocol MyViewProtocol: class {
    func reloadData()
}

protocol MyModelProtocol: MyModelProtocolNotify {
    func fetchControl()
}

protocol MyModelProtocolNotify: class {
    func addObserver(_ observer: Any, selector: Selector)
    func removeObserver(_ observer: Any)
}

protocol MyViewPresenterProtocol: class {
} 
// protocol宣言おわり---------------

class MyLiveView: UIView {
}
// -------------------------------
// View
class MyViewController: UIViewController {
    private var myTextView = UITextView(frame: CGRect(x: 20, y: 20, width: 220, height: 60))
    var presenter: MyViewPresenter!
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myTextView)
        presenter = MyViewPresenter(view: self)
        updateControl()
        
    }
    override func loadView() {
        super.loadView()
        view = MyLiveView()
    }
    
    @objc func updateControl(){
        // Presenterに取得処理を移譲
        presenter.updateModelControl()
    }
}
extension MyViewController: MyViewProtocol {
    // Presenterから呼ばれる処理
    func reloadData() {
        myTextView.text = "通知を受信" 
    }
}
// ------------------------
// Presenter
class MyViewPresenter: MyViewPresenterProtocol {
    var view: MyViewProtocol
    var model: MyModelProtocol
    
    init(view: MyViewProtocol) {
        self.view = view
        self.model = MyModel()
        // NotificationCenter
        model.addObserver(self, selector:#selector(self.updated))
    }
    // Modelに処理を移譲
    func updateModelControl(){
        model.fetchControl()
    }
    
    // Modelから更新の通知があったらViewに更新を依頼
    @objc func updated() {
        //print("updated!!!")
        view.reloadData()
    }
}

// --------------------
// Model
class MyModel: MyModelProtocol {
    
    
    func fetchControl(){
        // NotificationCenterでPresenterに通知
        self.notify()
    }
}

extension MyModel: MyModelProtocolNotify {
    var notificationName: Notification.Name {
        return Notification.Name(rawValue: "MyModel")
    }
    
    func removeObserver(_ observer: Any) {
        NotificationCenter.default.removeObserver(observer)
    }
    
    func notify() {
        NotificationCenter.default.post(name: notificationName, object:nil)
    }
    
    func addObserver(_ obserber: Any, selector: Selector) {
        NotificationCenter.default.addObserver(obserber, selector: selector, name: notificationName, object: nil)
    }
}

PlaygroundPage.current.liveView = MyViewController()

Swift Playgrounds向けアクセサリ「PlaygroundBluetooth API」を使ってBluetooth LEデバイスと通信をする

WWDC2017で発表されたPlaygroundsBluetooth API便利そう

今年最後のブログになります。
今何してあそんでるかの報告がわりに。

WWDC2017

What’s New in Swift Playgrounds - Apple WWDC 2017

iPad向けのSwift Playgroundsアクセサリとして提供されているPlaygroundBluetoothを使って見ました。CoreBluetoothベースですが、Appleが言うにはより一貫性がある(これはSwift PlaygroundsでのBluetooth通信のユーザエクスペリエンスが一貫していると言う意味だと思います)APIで、以下のコンポーネントを提供しています

PlaygroundBluetooth API

  • PlaygroundBluetoothCentralManager(および、そのDelegate)
    • Bluetooth機器との接続、通信 (CBCentralManagerに近い)
  • PlaygroundBluetoothConnectionView(および、そのDelegate)
    • Bluetooth機器の検出、接続、接続解除などのためのUI

特にPlaygroundBluetoothConnectionViewはUIを提供して検出、接続、解除までを一貫してしてくれるので、プログラマによってBluetooth検出、接続、解除時のUIが全く異なり、アプリを使うユーザが途方に暮れる…事は少なくなりそうです。
と言っても2017年12月現在、Appleのページで纏まった資料やサンプルコードが見当たりませんでした。探し方が下手なだけかも知れませんが。
2018年9月更新AppleのページにPlaygroundBluetooth Frameworkの詳細情報が掲載されています。最新情報はこちらを確認してください。

サンプルコードないかな…

唯一見つけたのが以下のサイト。
https://swiftexample.info/snippet/liveviewswift_bricklife_swift
iPhoneの電池残量が確認できるサンプルコード
なんか途中から全力で生のCoreBluetooth触りにいってるけど大丈夫かこのサンプルコード…。

カスタムプロファイルのペリフェラルとお話しする

これをベースに、カスタムプロファイルを持ったBluetoothペリフェラルに接続し、データをやり取りするターミナルっぽいやつを作りました。
iPadは終始セントラルとして動きます。

  • read
  • writeWithResponse
  • writeWithoutResponse
  • notify

キャラクタリスティックに対応します。

ペリフェラル側とその設定が必要です。
今回はセントラル(iPad)からきたデータをWrite(writeWithoutResponse)し、Readに対してはそのデータを返すようにします。(ループバックっぽい動作にする)

いきなりBLEを持ってる組み込みマイコンにさせると、バグったときに切り分けが出来なくなって詰みそうですので、今回はiPhoneにLightBlueアプリをいれて仮想的にペリフェラルとして動かしています。
VirtualPeripheralの提供するサービスは「Blank」を選択し

  • Service UUID : 0x1111
  • Characteristic UUID : 0x2222

を選択します。

PropertyはReadとWriteWithoutResponseにチェック

そうすると、iPadは以下のように動きます。

Communicate with BLE accessory using PlaygroundBluetooth API

参考資料


iOS 11 Programming

iOS 11 Programming

  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する

Swift Playgrounds(Swift4.0)で横スクロールするグラフを描く

単純な横スクロールのグラフだけでいい

iOSプログラミングのお仕事がちょいちょい来てるのですが、ブランクが長すぎてiPad Proでリハビリをする毎日です。Cordova/PhoneGapも最近聞かないなぁ。
最近はReactがアツいとかなんとか聞くのですが。

iOS11になってから今まで順調に動いていたPythonista3でグラフをプロットしようとすると何かの契機にアプリごとクラッシュしてしまうようになりました。
自分がmatplotlibの扱いがよくわかってないのもあるのですが…そのうち解決するでしょうということで。

Swiftでグラフを描くのはライブラリを使うのが良いようです。たとえばGitHub - core-plot/core-plot: Core Plot source code and example applications
今回は簡単な折れ線グラフが書きたいだけなのでこのライブラリの学習コストが見合わないなと思いいろいろ探していたところ、ありました。

Swiftでシンプルなグラフ描写する ... | FiNC Developers Blog

ここで紹介されているものをお借りしました。ありがとうございます。
プロット数が多くなると、UIViewサイズに合わせてx軸のマージンがどんどん小さくなるので、UIScrollViewを使って横スクロールしてしまうようにしました。またSwift 2.0の記述だったので4.0に対応するように一部直しています。
なお、プロット間のx軸の距離は80に固定してしまっています。複数の折れ線グラフも楽々。


f:id:Lynx-EyED:20171106153539p:plain

動作してるところ


Swift ScrollView plot

感想

iPad上のSwift Playgroundsで結構しっかりしたプログラム組めるのはすごいなと思いました。電車内やカフェで殴り書きするのにちょうどいい。
でも、ガチなソフト組むとなると、StoryBoardが欲しくなるしエディタとしては機能が限定されているので、MacXCodeが必要になりますね。

コード

本家
Swiftでシンプルなグラフ描写する ... | FiNC Developers Blog
のコードと比較したほうがいいかも。急いででっち上げてしまったので…

//
//  ViewController.swift
//  SimpleGraph
//
//  Created by Yoshimi Kondo on 2015/11/19.
//  Copyright © 2015年 yoshimikeisui. All rights reserved.
//
//  fixed to use UIScrollView by lynxeyed 2017/11/05


import PlaygroundSupport
import UIKit

public class ViewController: UIViewController{
    let wd = 500
    let ht = 630
override public func viewDidLoad() {
        super.viewDidLoad()
        drawLineGraph()
    //drawBarGraph()
    }
    
    func drawLineGraph() {
        let stroke1 = LineStroke(graphPoints: [1, 3, 6, 4, 9, 12, 4, 11, 15,5,1,5,10,5])
        let stroke2 = LineStroke(graphPoints: [16,3, nil, 6, 4, 8])
        let stroke3 = LineStroke(graphPoints: [24,5, 5, 6, 4, 5])
        
        stroke1.color = #colorLiteral(red: 0.239215686917305, green: 0.674509823322296, blue: 0.968627452850342, alpha: 1.0)
        stroke2.color = #colorLiteral(red: 0.854901969432831, green: 0.250980406999588, blue: 0.47843137383461, alpha: 1.0)
        stroke3.color = #colorLiteral(red: 0.584313750267029, green: 0.823529422283173, blue: 0.419607847929001, alpha: 1.0)
        let lineGraphView = UIScrollView()
        lineGraphView.frame.size = CGSize(width: wd, height: ht)
        let graphFrame = LineStrokeGraphFrame(strokes: [stroke1, stroke2, stroke3])
        lineGraphView.isPagingEnabled = false
        lineGraphView.backgroundColor = UIColor.darkGray
        view.addSubview(lineGraphView)
        lineGraphView.addSubview(graphFrame)
        let fw = graphFrame.xAxisPointsCount * Int(graphFrame.xAxisMargin)
        lineGraphView.contentSize = CGSize(width:fw, height:ht) //スクロールビュー内のコンテンツサイズ設定
       
        
    }
    
    func drawBarGraph() {
        let bars = BarStroke(graphPoints: [nil, 1, 3, 1, 4, 9, 12, 4])
        bars.color = UIColor.green
        
        let barFrame = LineStrokeGraphFrame(strokes: [bars])
        
        let barGraphView = UIView(frame: CGRect(x: 0, y: 240, width: view.frame.width, height: 200))
        barGraphView.backgroundColor = UIColor.gray
        barGraphView.addSubview(barFrame)
        
        view.addSubview(barGraphView)
    }
    
    override public func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
}

protocol GraphObject {
    var view: UIView { get }
}

extension GraphObject {
    var view: UIView {
        return self as! UIView
    }
    
    func drawLine(from: CGPoint, to: CGPoint) {
        let linePath = UIBezierPath()
        
        linePath.move(to: from)
        linePath.addLine(to: to)
        linePath.lineWidth = 0.5
        
        let color = UIColor.white
        color.setStroke()
        linePath.stroke()
        linePath.close()
    }
}

protocol GraphFrame: GraphObject {
    var strokes: [GraphStroke] { get }
}

extension GraphFrame {
    // 保持しているstrokesの中で最大値
    var yAxisMax: CGFloat {
        return strokes.map{ $0.graphPoints }.flatMap{ $0 }.flatMap{ $0 }.max()!
    }
    
    // 保持しているstrokesの中でいちばん長い配列の長さ
    var xAxisPointsCount: Int {
        return strokes.map{ $0.graphPoints.count }.max()!
    }
    
    // X軸の点と点の幅
    var xAxisMargin: CGFloat {
        //return view.frame.size /CGFloat(xAxisPointsCount)
        return 80.0 //    }
}

class LineStrokeGraphFrame: UIView, GraphFrame {
    
    var strokes = [GraphStroke]()
    convenience init(strokes: [GraphStroke]) {
        self.init()
        self.strokes = strokes
    }
    
    override func didMoveToSuperview() {
        if self.superview == nil { return }
        //self.frame.size = self.superview!.frame.size
        let uisview = ViewController()
        
        //var frameWidth = gs.
        self.frame = CGRect(x: 0, y: 0, width: 3000, height: uisview.ht )
        self.view.backgroundColor = UIColor.clear
        
        strokeLines()
    }
    
    func strokeLines() {
        for stroke in strokes {
            self.addSubview(stroke as! UIView)
        }
    }
    
    override func draw(_ rect: CGRect) {
        drawTopLine()
        drawBottomLine()
        drawVerticalLines()
    }
    
    func drawTopLine() {
        self.drawLine(
            from: CGPoint(x: 0, y: frame.height),
            to: CGPoint(x: frame.width, y: frame.height)
        )
    }
    
    func drawBottomLine() {
        self.drawLine(
            from: CGPoint(x: 0, y: 0),
            to: CGPoint(x: frame.width, y: 0)
        )
    }
    
    func drawVerticalLines() {
        for i in 1..<xAxisPointsCount {
            let x = xAxisMargin*CGFloat(i)
            self.drawLine(
                from: CGPoint(x: x, y: 0),
                to: CGPoint(x: x, y: frame.height)
            )
        }
    }
}


protocol GraphStroke: GraphObject {
    var graphPoints: [CGFloat?] { get }
}

extension GraphStroke {
    var graphFrame: GraphFrame? {
        return ((self as! UIView).superview as? GraphFrame)
    }
    
    var graphHeight: CGFloat {
        return view.frame.height
    }
    
    var xAxisMargin: CGFloat {
        return graphFrame!.xAxisMargin
    }
    
    var yAxisMax: CGFloat {
        return graphFrame!.yAxisMax
    }
    
    // indexからX座標を取る
    func getXPoint(index: Int) -> CGFloat {
        return CGFloat(index) * xAxisMargin
    }
    
    // 値からY座標を取る
    func getYPoint(yOrigin: CGFloat) -> CGFloat {
        let y: CGFloat = yOrigin/yAxisMax * graphHeight
        return graphHeight - y
    }
}


class LineStroke: UIView, GraphStroke {
    var graphPoints = [CGFloat?]()
    var color = UIColor.white
    
    convenience init(graphPoints: [CGFloat?]) {
        self.init()
        self.graphPoints = graphPoints
    }
    
    override func didMoveToSuperview() {
        if self.graphFrame == nil { return }
        self.frame.size = self.graphFrame!.view.frame.size
        self.view.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        let graphPath = UIBezierPath()
        
        graphPath.move(
            to: CGPoint(x: getXPoint(index: 0), y: getYPoint(yOrigin: graphPoints[0] ?? 0))
        )
        
        for graphPoint in graphPoints.enumerated() {
            if graphPoint.element == nil { continue }
            let nextPoint = CGPoint(x: getXPoint(index: graphPoint.offset),
                                    y: getYPoint(yOrigin: graphPoint.element!))
            graphPath.addLine(to: nextPoint)
        }
        
        graphPath.lineWidth = 5.0
        color.setStroke()
        graphPath.stroke()
        graphPath.close()
    }
}

class BarStroke: UIView, GraphStroke {
    var graphPoints = [CGFloat?]()
    var color = UIColor.white
    
    convenience init(graphPoints: [CGFloat?]) {
        self.init()
        self.graphPoints = graphPoints
    }
    
    override func didMoveToSuperview() {
        if self.graphFrame == nil { return }
        self.frame.size = self.graphFrame!.view.frame.size
        self.view.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        for graphPoint in graphPoints.enumerated() {
            let graphPath = UIBezierPath()
            
            let xPoint = getXPoint(index: graphPoint.offset)
            graphPath.move(
                to: CGPoint(x: xPoint, y: getYPoint(yOrigin: 0))
            )
            
            if graphPoint.element == nil { continue }
            let nextPoint = CGPoint(x: xPoint, y: getYPoint(yOrigin: graphPoint.element!))
            graphPath.addLine(to: nextPoint)
            
            graphPath.lineWidth = 30
            color.setStroke()
            graphPath.stroke()
            graphPath.close()
        }
    }
}

PlaygroundPage.current.liveView = ViewController()

通勤中にご飯が炊ける弁当箱 サーモスJBS-360を買ったらとてもよかったお話

時間がない

組み込みの記事しか書かないこのブログで書くネタじゃないかも何ですが、大体の方は会社勤め、学校通いですよね。
おひるごはん。
外食までもないとしてもコンビニ弁当おにぎりに頼りがち。
今から急いで客先に向かわなくては!…となるとカ□リーメイトのお世話に…

僕らは時間がないのです。朝に弁当の準備できるわけもない。んな時間あったらぎりぎりまで寝てるわ

Twitterでも話題になってた通勤中に御飯が炊けるThermos JBS-360。
通勤中にゴハンが炊ける弁当箱、サーモスが9月発売。お米の「おねば」を制御、美味しく炊けます - Engadget 日本版

9月の初めに東急ハンズで買いました。それが結構よかったんです。
みんな白米食おうぜ。QoL上がるよ。

これまでも電子レンジで御飯が炊ける触れ込みの容器、土鍋って結構あっていろんなものを試したのですが…
ぶっちゃけ言わしてもらうと、耐熱容器に水と米入れて、少し重めの蓋してチンすれば米くらい炊けます。
問題は、その吹きこぼれ。米を炊く度に電子レンジ毎回掃除するの、結構いやなので、やめてしまいました。

こいつは吹きこぼれないぞ!
あと、この吹きこぼれに粘度のある水分(おねば)が御飯をおいしくするそうで、吹きこぼれでレンジ汚さないだけではなく、ちゃんと吹き上がったおねばを戻してくれるのが良いところ。

使ってみる

開けます。今回はブラックを買いました。保温ケースがかっこいい。
f:id:Lynx-EyED:20170930224747j:plain

使い方説明が簡潔に書かれています。
f:id:Lynx-EyED:20170930225351j:plain


透明のごはん容器の目盛りに従い、米と水を入れます。今回は無洗米。柔らかめが好きな人は少しごはんの量減らして同量の水とかにすると良いのかも。
f:id:Lynx-EyED:20170930225200j:plain

この商品の要、炊飯パーツを取り付け
f:id:Lynx-EyED:20170930225230j:plain

電子レンジへ。500Wで8分です。朝の忙しい時間でも顔洗って歯を磨いて髪セットして着替えする時間より短いはず。
f:id:Lynx-EyED:20170930225309j:plain

レンジアップ完了。吹きこぼれも全くなくて感動しました
f:id:Lynx-EyED:20170930230223j:plain

あたりまえですが、容器がめっちゃ熱くなってます。無理矢理炊飯パーツと容器分離するとやけどします。分離せず、そのまま保温ケースにいれてから外します
f:id:Lynx-EyED:20170930230322j:plain

すぐに容器フタをします
f:id:Lynx-EyED:20170930230337j:plain

専用ポーチにいれて完成。行ってきます。(今気づいたけど値札とれよ>俺)
f:id:Lynx-EyED:20170930230504j:plain

食す

30分以上待てば蒸らしはOKです。さてお昼ご飯ですぞ。
f:id:Lynx-EyED:20171001000121j:plain

つやつやごはん
f:id:Lynx-EyED:20171001000721j:plain
食べたら、確かにうまい!この手のランチジャーはごはんがパサパサになりがちなのですが、もちもちです。

これは良い買い物だったなと思いました。ハンズで4000円弱。アマゾンでも最安値は同じくらいかな?


ビットコイン取引高日本一の仮想通貨取引所 coincheck bitcoin

iPad版Swift Playgroundsでsocket通信する

ネットワークにつながる組み込み機器とスマホを繋げるネタは数多ありますが、ほとんどがAndroid機器のようです。iOSバイスがそのような所謂"低レベル通信"のほとんどをユーザーに開放してないというのが理由のようです。
あとXCode使う=Mac買わなきゃ & お布施とかそういうやつ。

W5500(サーバー側)とiPad Pro(クライアント)だけでTCP/IPソケット通信できるといいなぁと探したらありました。

perl - How to implement a socket connection using a swift playground? - Stack Overflow

上記情報が古いバージョンのSwiftでいろいろ修正が必要でしたが、動作したのでメモがてら。
iPadのSwift Playgroundsアプリだけでプログラムできます。

これを現行のSwift 3.1用に修正しました。
下記例ではサーバーアドレス192.168.0.123:4567ポートと通信しています。(IP固定)

/* 
 Socket communication on Static IP
 Works on Swift 3.1 + playgrounds support
 */
import UIKit
import PlaygroundSupport

let addr = "192.168.0.123"
let port = 4567  //PORT Num

var buffer = [UInt8](repeating: 0, count: 255)

var inp : InputStream?
var out : OutputStream?
Stream.getStreamsToHost(withName: addr, port: port, inputStream: &inp, outputStream: &out)

if inp != nil && out != nil {
    let inputStream : InputStream = inp!
    let outputStream : OutputStream = out!
    inputStream.open()
    outputStream.open()
    
    if outputStream.streamError == nil && inputStream.streamError == nil {
        let queryString = "クライアントから送信したいデータ"
        let queryData = [UInt8](queryString.utf8) 
        while true {
            UnsafePointer<UInt8>(queryData)
            outputStream.write(queryData, maxLength: queryData.count)
            var readChars: Int = inputStream.read(&buffer, maxLength: buffer.count)
            if (readChars > 0) {
                let readString: String = NSString(data: NSData(bytes:buffer, length:readChars) as Data, encoding: String.Encoding.utf8.rawValue)! as String
                print(readString) //  サーバから受信したデータ
                usleep(300 * 1000) //300ms待つ(適当
                }
             
            } else {
                print ("server closed connection")
                inputStream.close()
                outputStream.close()
                break
            }
        }
    } else {
        print ("could not create socket")
    }
} else {
    print ("could not initialize stream")
}

通信中の切断に対する適切な対応とかタイムアウトとか全然考えてないので、そこら辺はご容赦。

受信したデータをprint()するだけの用途はあまりないと思うのでちょいと応用です。
趣味で作っている透過率測定器の結果をiPadに出力しました。この測定器はクライアントから「data?\r\n」という文字列を受信すると、ダイオードアレイのADCデータを「datais?t+1234567」という形式でクライアントへ送信します(欲しいデータは+1234567)。文字列から数値だけを抽出したいので、分割します。
先ほどの

print(readString) //  サーバから受信したデータ

を下記のコードに置き換えます。

//print(readString) //  サーバから受信したデータ

                if (readString.contains("datais?t")) {
                    let strSep = readString.components(separatedBy: "?t")
                    let rval = (strSep[1] as NSString).integerValue 
                    
                    usleep(300 * 1000) //300ms
                }
                

こちらのコードも変更

//let queryString = "クライアントから送信したいデータ"
let queryString = "data?\r\n"

Swift Playgroundsの便利機能で、変数の値変化をグラフの形でスコープ出来る(データ数上限あり)ので、簡単な確認なら気合い入れてグラフ描画コード書かなくても良い。

自分はWiznet W5500とWiFiルータ経由で通信をテストしました。
最近はやりのESP32とかXbee WifiとかWiFiが使えるデバイスで試した方がいれば、動作結果など是非お聞かせ願えれば。

参考:
Real-Time Communication with Streams Tutorial for iOS

iPad Pro 10.5用カバーでApple Pencil一緒に携帯できて頑丈なやつ。

Raspberry Pi 3でUrJTAGを使う

とりあえずRasPi3でUrJTAGが動くようにする

MAX10を出先や遠隔でコンフィグしたりするときRaspiでUrJTAGが使えたらなあと調べたら使えたのでメモ
RasPiだとGPIOでもバウンダリスキャンできるらしいので便利かもしれない。

apt install urjtagでインストールしたらUSB Blasterがうまく動かなかったので、仕方なくビルド。
参考はこちらUniversal JTAG library, server and tools / Discussion / Using UrJTAG: raspberry pi gpio port

準備

上記を参考に必要なパッケージをインストールする

$ sudo apt install autoconf autopoint libtool libreadline-dev python-dev libusb-dev libusb-1.0-0-dev flex libftdi-dev bison git

flex,bisonはBSDLをUrJTAGに食わせるのに共に必要なので追加しました。

D2XXのインストール。FTDIのサイトのREADMEを参考に行う。RasPi3は今のところOSが32bitなのでARMv7版を使うようだ。

$ wget http://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-arm-v7-hf-1.4.6.tgz
$ tar xvf libftd2xx-arm-v7-hf-1.4.6.tgz
$ cd release/build
$ sudo cp libftd2xx.* /usr/local/lib
$ sudo chmod 0755 /usr/local/lib/libftd2xx.so.1.4.6
$ sudo ln -sf /usr/local/lib/libftd2xx.so.1.4.6 /usr/local/lib/libftd2xx.so

補足:Raspberry Pi Zero/Pi 1/Compute Module(CM1)で同様のことをする場合
検索ワードから「Illegal instruction」でここに来られる方が多いです。このエラーメッセージが出てしまった場合はアーキテクチャが違うことを意味します。
Raspi Zero/Pi 1/Compute Module(CM1)はCPUがARM11系なのでアーキテクチャがARMv6になります。以下のようにしてください。

$ wget http://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-arm-v6-hf-1.4.8.tgz
$ tar xvf libftd2xx-arm-v6-hf-1.4.8.tgz
$ cd release/build
$ sudo cp libftd2xx.* /usr/local/lib
$ sudo chmod 0755 /usr/local/lib/libftd2xx.so.1.4.8
$ sudo ln -sf /usr/local/lib/libftd2xx.so.1.4.8 /usr/local/lib/libftd2xx.so

----補足ここまで----


ftd2xx.h,WinTypes.hがないとUrJTAGビルドでエラーになるのでコピーしておく

$ cd ../examples
$ sudo cp ftd2xx.h /usr/include/
$ sudo cp WinTypes.h /usr/include/


UrJTAGのビルド。下記サイトの内容そのまま
Universal JTAG library, server and tools / Discussion / Using UrJTAG: raspberry pi gpio port

$ cd ~/
$ git clone git://git.code.sf.net/p/urjtag/git urjtag-git
$ cd urjtag-git

ビルドの前にcmd_bfin.cの内容を修正

$ cd urjtag/src/cmd
$ nano cmd_bfin.c 

cmd_bfin.cソースコード先頭に下記を追加する

#ifndef _SYS_UCONTEXT_H
#define _SYS_UCONTEXT_H
#endif

ctrl-o,ctrl-xで保存後エディタ終了

UrJTAGのビルド

$ cd ../../
$ ./autogen.sh

Warningはちゃんと見ておくべし。
使いたい項目がyesになっているか確認する。
今回はUSB Blasterを使いたいのでD2XX関連とBSDL,SVFはyesになっているか確認。

urjtag is now configured for

  Libraries:
    libusb     : 1.0
    libftdi    : yes (no async mode)
    libftd2xx  : yes
    inpout32   : no

  Subsystems:
    SVF        : yes
    BSDL       : yes
    STAPL      : no

問題なかったらmakeする

$ make
$ sudo make install

動かん……

$ sudo jtag
jtag: error while loading shared libraries: liburjtag.so.0: cannot open shared object file: No such file or directory

共有ライブラリとして認識されてないんじゃないかな…

$ sudo ldconfig
$ sudo jtag

UrJTAG 0.10 #
Copyright (C) 2002, 2003 ETC s.r.o.
Copyright (C) 2007, 2008, 2009 Kolja Waschk and the respective authors

UrJTAG is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
There is absolutely no warranty for UrJTAG.

warning: UrJTAG may damage your hardware!
Type "quit" to exit, "help" for help.

jtag>

動いた…