2021年9月30日木曜日

ESP32 でブートボタンをハンドリングする

ESP32 でブートボタンをハンドリングする

概要

ESP32 の開発ボードにはブートボタンとリフレッシュボタンが実装されていることがよくあります
今回はそれらのビルトインのボタンを利用してボタンが押されたときに BLE の notify 情報を送信するようなデバイスを作成してみたいと思います

基本的にはブートピン (0 ピン)が 1 になったときを見れば OK です

環境

  • macOS 11.6
  • Arduino IDE 1.8.16
  • ESP-WROOM-32 devkitc v4

スケッチ

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 255;

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

void onButton() {
  // sedning integer 255 via notify
  pCharacteristic->setValue((uint8_t*)&value, 4);
  pCharacteristic->notify();
}

void setup() {
  Serial.begin(115200);
  pinMode(0, INPUT_PULLUP);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  // notify value no button pushed
  if (deviceConnected) {
    static uint8_t lastPinState = 1;
    uint8_t pinState = digitalRead(0);
    if (!pinState && lastPinState) {
      onButton();
    }
    lastPinState = pinState;
  }
  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}

解説

pinMode(0, INPUT_PULLUP); でブートピンの状態を読み込めるようにします
0 ピンの最後の状態と今の状態が異なっていたら onButton 関数をコールします

onButton 関数では setup で作成した pCharacteristic を使って notify します
notify する値は FF (255) を送信します

動作確認

LightBlue を使って確認します
ESP32 に接続して notify サービスを選択します そしてlisten for notification の状態にしてブートボタンを押してみると FF のデータが取得できるのが確認できると思います

2021年9月29日水曜日

ESP32 超入門

ESP32 超入門

概要

ESP32 は Wifi と Bluetooth を備えた Arduino 互換のマイコンボードです
Amazon で 1,000 円ほどで購入できます

今回は開発のセットアップ方法とサンプルのスケッチを書き込んで動作を確認してみたいと思います

環境

  • macOS 11.6
  • Arduino IDE 1.8.16
  • ESP-WROOM-32

開発ボードの追加

Arduino -> Preferences から追加します

ツール -> ボード -> ボードマネージャ で「esp32」と検索すると ESP32 用のボードが出てくるようになるのでインストールします

ボードのインストールができたら一度 Arduino IDE を再起動しましょう

ボードの変更

ツール -> ボード -> ESP32 Arduino -> ESP32 Dev Module

を選択します
ここは各自の ESP32 の開発ボードのタイプに合わせて変更してください
冒頭で紹介した ESP32 と同じ商品の場合は「ESP32 Dev Module」で OK です

サンプルスケッチの書き込み

とりあえず BLE デバイスとして動作するスケッチを書き込んでみます

#include "SimpleBLE.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

SimpleBLE ble;

void onButton(){
    String out = "BLE32 name: ";
    out += String(millis() / 1000);
    Serial.println(out);
    ble.begin(out);
}

void setup() {
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    pinMode(0, INPUT_PULLUP);
    Serial.print("ESP32 SDK: ");
    Serial.println(ESP.getSdkVersion());
    ble.begin("ESP32 SimpleBLE");
    Serial.println("Press the button to change the device's name");
}

void loop() {
    static uint8_t lastPinState = 1;
    uint8_t pinState = digitalRead(0);
    if(!pinState && lastPinState){
        onButton();
    }
    lastPinState = pinState;
    while(Serial.available()) Serial.write(Serial.read());
}

書き込み時のオプションは以下の通りです
基本的にはデフォルトのままでシリアルポートだけ接続した ESP32 を選択するようにしてください

書き込み時のログは以下の通りです

esptool.py v3.1
Serial port /dev/cu.usbserial-0001
Connecting........_
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ec:94:cb:6e:79:d0
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Flash will be erased from 0x0000e000 to 0x0000ffff...
Flash will be erased from 0x00001000 to 0x00005fff...
Flash will be erased from 0x00010000 to 0x000ecfff...
Flash will be erased from 0x00008000 to 0x00008fff...
Compressed 8192 bytes to 47...
Writing at 0x0000e000... (100 %)
Wrote 8192 bytes (47 compressed) at 0x0000e000 in 0.2 seconds (effective 341.6 kbit/s)...
Hash of data verified.
Compressed 17984 bytes to 12318...
Writing at 0x00001000... (100 %)
Wrote 17984 bytes (12318 compressed) at 0x00001000 in 0.5 seconds (effective 268.9 kbit/s)...
Hash of data verified.
Compressed 903840 bytes to 600114...
Writing at 0x00010000... (2 %)
Writing at 0x0001abc2... (5 %)
Writing at 0x000264bd... (8 %)
Writing at 0x0002c108... (10 %)
Writing at 0x0003199d... (13 %)
Writing at 0x00037107... (16 %)
Writing at 0x0003c589... (18 %)
Writing at 0x00041e50... (21 %)
Writing at 0x000475c0... (24 %)
Writing at 0x0004cb62... (27 %)
Writing at 0x00051fac... (29 %)
Writing at 0x00057404... (32 %)
Writing at 0x0005d6c2... (35 %)
Writing at 0x00062e13... (37 %)
Writing at 0x000688cc... (40 %)
Writing at 0x0006e440... (43 %)
Writing at 0x00073bbb... (45 %)
Writing at 0x0007960a... (48 %)
Writing at 0x0007f3e3... (51 %)
Writing at 0x00084cbd... (54 %)
Writing at 0x0008a4df... (56 %)
Writing at 0x0008fc66... (59 %)
Writing at 0x00095411... (62 %)
Writing at 0x0009b008... (64 %)
Writing at 0x000a02e4... (67 %)
Writing at 0x000a5ab2... (70 %)
Writing at 0x000ab2e0... (72 %)
Writing at 0x000b1079... (75 %)
Writing at 0x000b73ff... (78 %)
Writing at 0x000bd1dc... (81 %)
Writing at 0x000c2a84... (83 %)
Writing at 0x000c8d11... (86 %)
Writing at 0x000d11a2... (89 %)
Writing at 0x000d7d26... (91 %)
Writing at 0x000ddbc9... (94 %)
Writing at 0x000e3887... (97 %)
Writing at 0x000e8ed4... (100 %)
Wrote 903840 bytes (600114 compressed) at 0x00010000 in 10.5 seconds (effective 688.2 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 128...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (128 compressed) at 0x00008000 in 0.1 seconds (effective 300.3 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

動作確認

iOS アプリの LightBlue を使って接続確認します
SimpleBLE.h を使っている場合はアドバタイズ名は「SimpleBLE」になります
PIN コードなどはないのですぐに接続することができると思います

シリアルコンソールを開き速度を「115200bps」にセットします
そして ESP32 上に実装されているブートボタンを押すとアドバタイズ名が変更されるのが確認できると思います

ちなみにサービスは実装されていないのでデバイスを選択しても接続することはできません
サービスの実装方法などもそのうち紹介できればなと思っています

最後に

ESP32 の開発環境の構築とスケッチの書き込み方法を紹介しました

ヘッダファイルが見つからないなどのエラーが出る場合はちゃんと開発ボードの追加ができているか確認しましょう
また選択している開発ボードが間違っていないかも確認しましょう

参考サイト

ESP32 で Notify 用のサービスを実装してみた

ESP32 で Notify 用のサービスを実装してみた

概要

サンプルのコードにサービス+Notify の構成があるのでそれを書き込んで動作確認してみました

開発環境のセットアップなどは前回の記事を参考にしてください

環境

  • macOS 11.6
  • Arduino IDE 1.8.16
  • ESP-WROOM-32

スケッチ

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};



void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
    // notify changed value
    if (deviceConnected) {
        pCharacteristic->setValue((uint8_t*)&value, 4);
        pCharacteristic->notify();
        value++;
        delay(1000); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}

動作確認

LightBlue で ESP32 に接続します
そして Notify のサービスを選択して「Listen for notification」をタップすると 1 秒おきにデータが飛んでくるのが確認できると思います

最後に

サンプルコード内にコメントもありアドバタイズ名の変更やサービスの追加、キャラクタリスティックの追加はこれをベースにすれば簡単にできるかなと思います

実際に使う場合には UUID の部分だけ他と被らないように注意しましょう

参考サイト

2021年9月27日月曜日

Ruby でパスワードを暗号化してコード上で補完する保存する方法

Ruby でパスワードを暗号化してコード上で補完する保存する方法

概要

Ruby のコード上またはコンフィグファイルなどでパスワードを管理する場合があると思います 平文だとさすがにあぶないので何かしらの方法で暗号化して記載する必要があるかなと思います

今回は bcrypt を使って暗号化して保存する方法を紹介します

環境

  • macOS 11.6
  • Ruby 3.0.2p107
  • bcrypt 3.1.16

準備

  • bundle init
  • vim Gemfile
gem "bcrypt"
  • bundle install

暗号化されていないコード

まずは暗号化されていないコードを紹介します パスワードが平文で保存されているのがわかるかなと思います

require 'io/console'

password = "hoge"
input_password = STDIN.noecho(&:gets).chomp
puts input_password == password

これを実行する場合には以下のようにします

  • bundle exec ruby test.rb

これでターミナル上でパスワードの入力を求められるので適当に文字列を入力しましょう
当然「hoge」と入力した場合は true が出力されます

見ての通り認証するためのパスワード情報を平分で管理しているので危険です

パスワードを暗号化して管理する

bcrypt を使います

require 'io/console'
require 'bcrypt'

password = BCrypt::Password.new("$2a$12$8HvnCbkJHhqJMtXdmgAwJeNOlOpSubId3yvk64RC/JqXW8uNZ3Vti") # => hoge
input_password = STDIN.noecho(&:gets).chomp
puts password == input_password

同じように実行してみましょう

  • bundle exec ruby test.rb

これで同じように「hoge」を入力すると true が出力されるのが確認できます

暗号化されたパスワードを生成する方法は

BCrypt::Password.create("hoge")

でできます

ポイント

BCrypt::Password.new で生成したオブジェクトの == メソッドを使うのがポイントです

もし上記のコードで比較の部分を以下のように書き換えると例えパスワードが合っていても false が返ります

puts password == input_password

これは String クラスの == を使っているのでエラーになっています

input_password を puts してみるとわかりますが暗号化されたままのパスワードが表示されます なので単純に文字列クラスの比較を使ってもエラーになるので BCrypt::Password クラスのオブジェクトを元に比較するのが重要です

最後に

今回は Ruby 上だけで解決する方法を紹介しました
データベースなどを使えばデータベース側の暗号化機能も使えるので Ruby 側で考える必要はなくなります

基本的に復号化できないのでハッシュ化されたパスワードが漏洩しても元のパスワードを取得するのができないのが bcrypt のメリットです
ただ逆に言うと復号化して使うことはできないのでコード上のパスワードを情報を使って外部に認証する場合などはこの方法は使えません

あくまでもこの方法はユーザからの入力に対して暗号化する手法になります

とにかく重要なのはコードやデータベース上に平文のパスワードなどは保存しないようにすることが重要です そもそも漏洩しないことが重要ですが

2021年9月24日金曜日

docker で monero のマイニング

docker で monero のマイニング

概要

もしリソースが余っている場合はマイニングするのもいいかもしれません

環境

  • macOS 11.5
  • docker 20.10.7

Monero Wallet のダウンロードとインストール

ここから Mac 用のウォレットアプリをダウンロードしインストールします

執筆時点では以下のようにインストールしました

  • Simpleモードでインストール
  • 新規でウォレットの作成

ウォレットのパスワードと途中に表示される秘密のパスワードは必ず忘れないようにしましょう

初回起動時はデーモン同期で時間がかかるので待ちましょう

Wallet アドレスの取得

Monero ウォレットアプリで確認できます   「受け取る」->「Address」で表示されるランダムな文字列がウォレットアドレスになります

Primary Address とも書いてあります

イメージの作成

今回はmonero-miner-dockerを使います

コンテナ起動

あとはコンテナを起動するだけです
WALLET 部分には取得したウォレットアドレスを記載します

  • docker run -d -m 4g --name monero -e WALLET=“44pxxxxxxxxxxxxxxxxxxxx” monero-cpu-miner

メモリは 4g 設定していますが 3g でも大丈夫です

動作確認

問題なくコンテナが動作していれば大丈夫です
30 分くらいしたら https://supportxmr.com/ で自分のウォレットアドレスを入力して見つかるか確認しましょう

もし見つからない場合はまだマイニングできていません
ウォレットアドレスやマイニングプールにちゃんと接続できる環境かなどログから調べてください

その他

デフォルトのマイニングプールは pool.supportxmr.com:5555 になっています

手元の Mac で試した感じだと出ても 300H/s くらいでした
Monero に換算すると一日頑張っても 0.02 XMR (2ドル) くらいです

電気代と釣り合わないもしくはマシンがオーバーヒートするような場合はマイニングを停止しましょう

クラウドでは間違ってもやらないようにしましょう
マイニングができるクラウドサービスもありますが GCP の無料プランなどは規約に違反することになりアカウントが BAN される可能性もあるので注意してください

GPU も使いたい場合は別のイメージを使いましょう

MSR モジュールが使えないと高速モードで動作しないので mac ではなく Linux 環境のほうがいいのかもしれません

2021年9月22日水曜日

Python でコードから UML (クラス図) を生成する方法

Python でコードから UML (クラス図) を生成する方法

概要

pyreverse コマンドは pylint モジュールに取り込まれているのでそちらを使うのがポイントです

環境

  • macOS 11.5.2
  • Python 3.8.3
  • pyreverse in pylint 2.10.2

インストール

pylint とインストールすると pyreverse もインストールされます

  • pipenv install pylint

テストコード

class User:

    def __init__(self, name, age):
       self.name = name
       self.age = age

    def show(self):
        print("{} => {}".format(self.name, self.age))

class SuperUser(User):

    def __init__(self, name, age):
        super().__init__(name, age)
        self.admin = True

    def show(self):
        super().show()
        print(self.admin)


u1 = User("hawk", 10)
u1.show()
admin = SuperUser("snowlog", 20)
admin.show()

動作確認

  • pipenv run pyreverse -o png test.py
  • open classes.png

最後に

pyreverse 単体ではほぼメンテされてませんが pylint 側に取り込まれているようなので pylint を使える環境であればそちらの pyreverse を使いましょう

2021年9月21日火曜日

Ruby で UML を出力する

Ruby で UML を出力する

概要

Ruby のコードから UML を出力するのに xumlidot というツールを使ってみました
使い方と出力結果を紹介します

注意点として Ruby3 系には対応していません https://github.com/os6sense/xumlidot/issues/36

環境

  • macOS 11.5.2
  • Ruby 2.6.6 3.0.2p107
  • xumlidot 0.1.0

Ruby 2.6.6 のインストール

Ruby3 系だと動作しないので 2.6.6 をインストールします

  • rbenv install 2.6.6

インストール

bundler でインストールします
pry にも依存しているのでインストールします

  • bundle init
  • vim Gemfile
gem "xumlidot"
gem "pry"
  • bundle instsall

テストコード

gem を作成するときの構成っぽい感じで作成していますが test.rb だけ作成しても OK です

  • mkdir -p lib/user
  • vim lib/user.rb
require 'rubygems'
require 'fileutils'

require_relative 'user/test'

module User
end
  • vim lib/user/test.rb
module User
  class User
    def initialize(name, age)
      @name = name
      @age = age
    end

    def show
      puts "#{@name} => #{@age}"
    end

    attr_accessor :name, :age
  end

  class SuperUser < User
    def initialize(name, age)
      super(name, age)
      @admin = true
    end

    def show
      super
      puts @admin
    end
  end
end

動作確認

  • bundle exec xumlidot lib > test.dot
  • dot -Tpng test.dot -o test.png
  • open test.png

これで以下のようなクラス図が生成されていれば OK です

最後に

Ruby3 系ですでに進めてしまっているプロジェクトに適用できなくはないですがいろいろ面倒なので素直に諦めましょう

参考サイト

2021年9月17日金曜日

begoo でモデルを生成して JSON リクエストを受け取る方法

begoo でモデルを生成して JSON リクエストを受け取る方法

概要

beego には専用の CLI ツールがありこれを使うとプロジェクトの雛形を作成できます
シリアライズが必要な JSON などのデータはモデルが必要になり bee CLI を使うとその辺りのモデルも簡単に作成してくれます

今回は bee CLI を使ってプロジェクトの作成を行いモデルを使って POST メソッドで JSON を受け取る方法を紹介します

環境

  • macOS 11.5.2
  • golang 1.17
  • beego 2.0.1

bee コマンドのインストール

プロジェクトの作成

  • cd $GOPATH/src/github.com/hawksnowlog
  • bee new post_test

依存関係の解決

  • cd $GOPATH/src/github.com/hawksnowlog/post_test
  • go mod tidy

とりあえず動かす

  • cd $GOPATH/src/github.com/hawksnowlog/post_test
  • bee run

これで localhost:8080 にアクセスして以下の画面が表示されることを確認します

コントローラの編集

デフォルトのコントローラを編集して POST を受け取れるようにしてみます

  • cd $GOPATH/src/github.com/hawksnowlog/post_test
  • vim controllers/default.go
package controllers

import (
	"encoding/json"
	beego "github.com/beego/beego/v2/server/web"
)

type MainController struct {
	beego.Controller
}

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func (c *MainController) Post() {
	var p Person
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &p); err != nil {
		c.Ctx.WriteString("json parse error")
		return
	}
	c.Data["json"] = &p
	c.ServeJSON()
}

ポイントは Person 構造体を作成する点とそれを使って RequestBody から json.Unmarshal する点です

設定ファイルの修正

Input.RequestBody をコントラーラで参照する場合は config ファイルの変更も必要になります

copyrequestbody = true を追記します

  • cd $GOPATH/src/github.com/hawksnowlog/post_test
  • vim conf/app.conf
appname = post_test
httpport = 8080
runmode = dev
copyrequestbody = true

動作確認

これで再度 bee run しましょう

  • cd $GOPATH/src/github.com/hawksnowlog/post_test
  • bee run

あとは JSON + POST でリクエストするとリクエストした JSON が返却されるのが確認できると思います

  • curl -v -XPOST localhost:8080 -H 'content-type: application/json' -d '{"name":"hawksnowlog","age":100}'

最後に

beego を始める場合は bee new でプロジェクトをちゃんと作成してあげたほうが必要なディレクトリの作成もしてくれるので開発はしやすくなるかなと思います

参考サイト

2021年9月16日木曜日

beego 超入門

beego 超入門

概要

beego は golang 製の Web フレームワークです
今回はインストールから簡単な使い方を紹介します

環境

  • macOS 11.5.2
  • golang 1.17
  • beego 2.0.1

初期化

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go mod init

サンプルコード作成

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • vim main.go
package main

import (
	"github.com/beego/beego/v2/server/web"
)

type MainController struct {
	web.Controller
}

func (this *MainController) Get() {
	this.Ctx.WriteString("hello world")
}

func main() {
	web.Router("/", &MainController{})
	web.Run()
}

依存関係解決

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go mod tidy

ビルド

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go build

動作確認

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • ./test
  • curl localhost:8080

=> hello world

最後に

ドキュメントは中国語が多い印象です

参考サイト

2021年9月15日水曜日

go mod 超入門

go mod 超入門

概要

過去に go dep を使ったパッケージ管理方法を紹介しました
現在は go mod を使う方法が主流なので go mod を使ったパッケージ管理の方法を紹介します

環境

  • macOS 11.5.2
  • golang 1.17

サンプルコード

以下のコードを実行するために依存関係を go mod を使って解決します
Sirupsen/logrus という外部のパッケージを使っています

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • vim main.go
package main

import (
	log "github.com/Sirupsen/logrus"
)

func main() {
	log.WithFields(log.Fields{
		"key": "value",
	}).Info("logrus test")
}

初期化

まずは初期化します
これで go.mod ファイルが作成されます

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go mod init

依存関係の解決

スクリプト内で使用しているパッケージを自動的に読み取ってインストールしてくれます
また go.mod が更新されて依存関係を自動で追記してくれる他 go.sum も生成されます

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go mod tidy
  • cat go.mod
module github.com/hawksnowlog/test

go 1.17

require github.com/sirupsen/logrus v1.8.1

require golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect

ちなみに上記モジュールがインストールされるパスは

$GOPATH/pkg/mod/github.com/sirupsen/logrus@v1.8.1

でした

ビルド

あとはビルドするだけです
バイナリが生成されるか確認しましょう

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • go build

実行

カレントにバイナリが生成されているので実行してみましょう
ちゃんと外部のモジュールが使われてエラーにならないことが確認できます

  • cd $GOPATH/src/github.com/hawksnowlog/test
  • ./test
INFO[0000] logrus test key=value

外部パッケージを追加したい場合は

普通に go get を使います

これで自動的に go.mod にも依存関係が追記されます

  • cat go.mod
module github.com/hawksnowlog/test

go 1.17

require github.com/sirupsen/logrus v1.8.1

require (
        github.com/gomodule/redigo v1.8.5 // indirect
        golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
)

あとは普通にスクリプト内で import すれば OK です
もしスクリプトのどこでも使われていない場合に再度 go mod tidy すると自動で依存関係を削除してくれます

最後に

go mod help で詳しい説明も確認できます
比較的新しい golang であれば標準で搭載されているのでこちらを使いましょう

参考サイト

2021年9月14日火曜日

marshmallow の post_dump の挙動を確認してみた

marshmallow の post_dump の挙動を確認してみた

概要

marshmallow の load と dump 時に自動的にコールされる pre_ post_ 系のデコレータの処理の挙動を確認してみました

環境

  • macOS 11.5.2
  • Python 3.8.3
  • marshmallow 3.13.0

インストール

  • pipenv install marshmallow

サンプルコード

from marshmallow import (
    Schema, pre_load, post_load, post_dump, fields
)

class User():
    def __init__(self, email, age):
        self.email = email
        self.age = age

class UserSchema(Schema):

    email = fields.Str(required=True)
    age = fields.Integer(required=True)

    @pre_load(pass_many=True)
    def remove_envelope(self, data, many, **kwargs):
        # ルート要素 (result or results) を data から取得する
        # 取得した data[namespace] は post_load で item に格納される
        namespace = 'results' if many else 'result'
        return data[namespace]

    @post_load
    def lowerstrip_email(self, item, many, **kwargs):
        # 各 item に格納されている値を加工する場合はこちらを使います
        # many=True の場合は自動的に複数回コールされます
        item['email'] = item['email'].lower().strip()
        return item

    @post_dump(pass_many=True)
    def add_envelope(self, data, many, **kwargs):
        # many=True の場合は results をルートキーとする dict を返却します
        namespace = 'results' if many else 'result'
        return {namespace: data}

schema = UserSchema()
result = schema.load({"result": {"age": "1", "email": "test1@mail"}})
print(result)
result = schema.load({"results": [{"age": "2", "email": "test2@mail"}, {"age": "3", "email": "test3@mail"}]}, many=True)
print(result)

user4 = User("test4@mail", 4)
result = schema.dump(user4)
print(result)
user5 = User("test5@mail", 5)
user6 = User("test6@mail", 6)
result = schema.dump([user5, user6], many=True)
print(result)

解説、挙動の確認

schema.load がコールされた場合には pre_load と post_load が自動的にコールされます
schema.dump がコールされた場合は pre_dump と post_dump が自動的にコールされます

load は dict -> dict の変換します
指定したキーの値配下の情報を取得しています

dump は class -> dict の変換をします
こちらのほうがよく使う手法になるかなと思います

pre_ ではデータの加工は行わず必要なデータの取得などを行います
post_ 側でデータを加工するので post_ 側に渡すデータを必ず return する必要があります
many=True の場合は post_ が自動的に複数回コールされて各要素分だけ処理されます

最後に

  • pre_ はデータの加工をする対象の抽出
  • post_ でデータの加工をする

というのが主な使い方になるかなと思います

参考サイト

2021年9月13日月曜日

emacs で HTTP リクエストを送信してみた

emacs で HTTP リクエストを送信してみた

概要

emacs 上で curl 的なことがしたい場合は request パッケージが使えます

環境

  • macOS 11.5.2
  • emacs 27.2
  • request 20210816.200

request のインストール

  • M-x package-list-packages
  • request を選択してインストール

.emacs 編集

動作確認は .emacs にリクエストを書いて行います
あとはバッファで eval-buffer します

GET

レスポンスは data に入ってきます
json-read を使うことで JSON レスポンスをパースしてくれます
assoc-default で指定したキー (params) の値を取得して表示しています

(require 'request)
(request "https://kaka-request-dumper.herokuapp.com/"
  :params '(("key" . "value") ("key2" . "value2"))
  :parser 'json-read
  :success (cl-function
            (lambda (&key data &allow-other-keys)
              (message "I sent: %S" (assoc-default 'params data)))))

POST

JSON を POST する場合は headers を設定すれば OK です

(require 'request)
(request "https://kaka-request-dumper.herokuapp.com/"
  :type "POST"
  :headers '(("Content-Type" . "application/json"))
  :data (json-encode '(("name" . "hawksnowlog") ("age" . "20")))
  :parser 'json-read
  :success (cl-function
            (lambda (&key data &allow-other-keys)
              (message "I sent: %S" (assoc-default 'body data)))))

最後に

外部からデータを取得したりするときに使えます
SDK などのパッケージを作るときには重宝するかもしれません

参考サイト

2021年9月10日金曜日

ターミナル上で開いた emacs から macOS 上に通知する方法

ターミナル上で開いた emacs から macOS 上に通知する方法

概要

alert というパッケージがあるのでこれを使います
macOS の場合 terminal-notifier を使うのがポイントです

環境

  • macOS 11.5.2
  • emacs 27.2
  • terminal-notifier 2.0.0

terminal-notifier のインストール

  • brew install terminal-notifier

/usr/local/bin/terminal-notifier にインストールされました
これを PATH に追加して emacs 側がコマンドを叩けるようにしましょう

alert のインストール

package.el を使います

  • M-x package-list-packages
  • alert を探してインストール

.emacs の設定

ポイントは alert-default-style をセットする点です
これがないとうまくアラートが飛びません

(require 'alert)
(setq alert-default-style 'notifier)
(alert "Test" :style #'notifier)
(alert "Test with title" :style #'notifier :title "Title")

動作確認

emacs を再度起動するか .emacs ファイルを eval-buffer しましょう
右上に通知が来れば OK です

最後に

オプションでアイコン (-appIcon) を設定できます
現在だと指定可能なオプションはそれくらいのようです
https://github.com/jwiegley/alert/blob/master/alert.el#L852

関数としてコールする方法しかなくミニバッファで実行することはできないので注意しましょう

参考サイト

2021年9月9日木曜日

treemacs を emacs 起動時に起動する方法

treemacs を emacs 起動時に起動する方法

概要

タイトルの通りです 普通に .emacs に書いてもダメで少し工夫が必要です

環境

  • Ubuntu 16.04
  • emacs 27.1

方法

(save-selected-window (treemacs-select-window))

ダメな方法

(treemacs)

これだと scratch バッファも開いてしまう

参考サイト

2021年9月8日水曜日

emacs で Python の lsp サーバと連携する方法

emacs で Python の lsp サーバと連携する方法

概要

Python の LSP サーバを導入して emacs と連携してみました
サーバは python-lsp/python-lsp-server を使い emacs 側からは lsp-mode を使います

環境

  • macOS 11.5.2
  • emacs 27.1
  • python-lsp-server 1.2.1
  • Python 3.8.3

python-lsp-server のインストール

  • pip install 'python-lsp-server[all]'

pylsp というコマンドがグローバルにインストールされ使えるようになれば OK です

lsp-mode の設定

  • vim .emacs
(require 'lsp-mode)
(add-hook 'python-mode-hook #'lsp)

動作確認

Python ファイルを開けば lsp が自動で起動します

  • M-. ジャンプ
  • M-x ジャンプ戻る
  • TAB or C-M-i 補完

最後に

python-launguage-server というサーバ実装もありますがこちらはもうメンテされていないので python-lsp-server を使いましょう

標準パッケージは補完してくれますが自分で作成したクラスから作成したオブジェクトのメソッド名の補完などはやってくれませんでした

ただ新規作成したクラスに対しては補完してくれたので設定が足りていないだけかなと思います

参考サイト

2021年9月7日火曜日

flymake-ruby をインストールする方法

flymake-ruby をインストールする方法

概要

flymake-ruby をインストールする方法を紹介します

環境

  • macOS 11.5.2
  • emacs 27.1

flymake-ruby のインストール

これ を使って solargraph からの結果を flymake で表示します

  • M-x package-list-packages
  • flymake-ruby をインストール

.emacs の設定

以下を追記します
ruby ファイルを開いたときに ruby-mode になるのでそれをフックし flymake-ruby も起動します

(require 'flymake-ruby)
(add-hook 'ruby-mode-hook 'flymake-ruby-load)

最後に

他に必要な設定がなくシンプルで使いやすいのが良いと思います

2021年9月6日月曜日

emacs の tramp モードで鍵認証する方法

emacs の tramp モードで鍵認証する方法

概要

ssh する際に鍵認証が必要な場合は事前に .ssh/config に鍵のパスを記載しておく必要があります

環境

  • Ubuntu 16.04
  • emacs 27.1

tramp モードのコマンド

tramp モードでは単純に ssh でファイルをオープンするコマンドを実行します
この際に hostname サーバに接続するには鍵認証が必要だとします

  • C-x C-f /ssh:username@hostname:/path/to/file

.ssh/config に IdentityFile に鍵のパスを指定する

tramp コマンド側で鍵の指定をするのはいろいろと面倒なので .ssh/config に認証時の情報を記載しましょう
IdentityFile を使えば鍵のパスが記載できます

  • vim ~/.ssh/config
Host hostname
  HostName 127.0.0.1
  User username
  Port 22
  IdentityFile /Users/username/.ssh/id_rsa

最後に

まずは事前に emacs を起動している端末からターゲットのホストに普通に ssh できるように .ssh/config を設定しましょう

あとはターミナルで実行したコマンド同様のことを tramp モード上で実行するだけです

2021年9月3日金曜日

lsp-mode + solargraph 設定方法

lsp-mode + solargraph 設定方法

概要

過去に eglot + solargraph を使った方法を紹介しましたが lsp-mode を使うほうがオススメです

環境

  • macOS 11.5.2
  • emacs 27.1
  • lsp-mode 20210831.1901
  • solargraph 0.43.0

solargraph のインストール

今回は bundler 配下で管理します
プロジェクトがそもそも bundler で管理されている場合は bundler 配下の gem も補完対象にするためにグローバルな solargraph ではなく bundler 配下にインストールした solargraph を使う必要があります

  • vim Gemfile
gem "solargraph"
  • bundle install

また yardoc が必要なので生成します

  • bundle exec yard gems
  • bundle exec solargraph bundle
  • bundle exec solargraph download-core

lsp-mode のインストール

package.el から lsp-mode をインストールします

  • M-x package-list-packages

.emacs 設定

ruby-mode で自動起動する設定と bundler 配下の solargraph を仕様する設定を追加します

(require 'lsp-mode)
(setq lsp-solargraph-use-bundler t)
(add-hook 'ruby-mode-hook 'lsp)

lsp-mode を起動するのではなく lsp を起動するのがポイントです

またプロジェクトの初回オープン時には以下のような確認が出ることがあるのでプロジェクトのルートパスを確認して選択しましょう

動作確認

あとは .rb ファイルを開けば OK です
自動で bundler 配下の solargraph を起動してくれます

基本的な操作は以下の通りです

  • M-. カーソルのあるモジュールやメソッドへジャンプする
  • M-, ジャンプ元に戻る
  • C-M-i 補完候補の表示

トラブルシューティング

うまくジャンプや補完が動作していない場合は bundler 配下の solargraph ではなくグローバルの solargraph が起動している可能性があります

一度グローバル側の solargraph を削除して再度 lsp-mode を起動して挙動を確認してみましょう

その他

  • require_relative で参照している先の ruby ファイルは補完してくれない (設定ミス?)
  • lsp-ruby や lsp-ui をインストールする必要はない
  • 生成したオブジェクトから参照可能なメソッドや変数を補完してくれない
    • 原因は module に配下にあるメソッドなどは補完対象ではないためです
    • gmail などの gem は module ベースでオブジェクトを生成するため生成したオブジェクト配下のメソッドは solargraph では参照できないためです
    • その場合は bundle exec irb か公式のリファレンスを参照しましょう
  • 複数のプロジェクトがある場合には先に起動した lsp を参照してしまうのでワークスペースを分けてプロジェクトを管理しましょう

最後に

eglot の場合 flymake がうまく動作していなかったのですが lsp-mode はうまく動作しました

require_relative を参照しないなど完全に動作するわけではないですが十分強力な機能かなと思います

(robe よりもいいかも 参考)

参考サイト