2019/08/28

C++でリングバッファを実装

背景


仕事でプログラミングする際に、リングバッファを多用するため、テンプレートを作成する

記事の目的


リングバッファのテンプレートを作成する

リングバッファ


ここでは、リングバッファの記述方法について記載する。

仕様

テンプレートの仕様について記載する。
  • 複数のスレッドから、バッファサイズの変更、バッファの書き込み、バッファの読み込みが可能
  • データが格納されていないバッファにアクセスすると、エラーが表示され、デフォルトのデータが返される

テンプレート

リングバッファを使用するテンプレートを記載する。
// ring_buffer.cpp
#include <stdio.h>
#include <mutex>

// リングバッファに格納するデータ形式
struct Data
{
    int number = 0;
};

// 前方宣言
class RingBuffer
{
    public:
        // コンストラクタ(リングバッファのサイズを指定(2の冪乗が最適))
        RingBuffer(uint buffer_size_in);
        // デストラクタ
        ~RingBuffer();
        // リングバッファのサイズを指定(0より大の整数、2の冪乗が最適)
        void setBufferSize(uint buffer_size_in);
        // リングバッファのサイズを取得
        uint getBufferSize();
        // リングバッファにデータを書き込み
        void writeBuffer(Data data_in);
        // リングバッファのデータを読み込み(0: 最新, BufferSize: 最古)
        Data readBuffer(uint read_pointer_in);
    private:
        // コンストラクタ(宣言時に必ずバッファサイズを宣言する)
        RingBuffer();
        // リングバッファのポインタ
        Data *data_;
        // リングバッファのサイズ
        uint buffer_size_   = 0;
        // リングバッファ内に書き込まれたデータ数
        uint data_size_     = 0;
        // 書き込みポインタ(最新データのポインタ)
        uint write_pointer_ = 0;
        // mutex(読み書き、バッファサイズ変更を排他)
        std::mutex mtx_;
};
// コンストラクタ(プライベート関数)
RingBuffer::RingBuffer()
{}
// コンストラクタ(リングバッファのサイズを指定)
RingBuffer::RingBuffer(uint buffer_size_in)
{
    // リングバッファのサイズを指定、メモリ上にバッファを確保
    setBufferSize(buffer_size_in);
}
// デストラクタ(リングバッファを削除)
RingBuffer::~RingBuffer()
{
    if (buffer_size_ > 0)
    {
        delete[] data_;
    }
}
// リングバッファのサイズを指定、メモリ上にバッファを確保
void RingBuffer::setBufferSize(uint buffer_size_in)
{
    if (buffer_size_in > 0)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // すでにメモリ上にリングバッファが確保されている場合は、一旦削除
        if (buffer_size_ > 0)
        {
            delete data_;
        }
        // リングバッファのサイズを保持
        buffer_size_ = buffer_size_in;
        // 内部変数の初期化
        data_size_   = 0;
        write_pointer_   = 0;
        // メモリ上にリングバッファを確保
        data_ = new Data[buffer_size_in];
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Please set buffer size more than 0.\n");
    }
}
// リングバッファのサイズを取得
uint RingBuffer::getBufferSize()
{
    // 排他
    std::lock_guard<std::mutex> lock(mtx_);
    return buffer_size_;
}
// リングバッファにデータ書き込み
void RingBuffer::writeBuffer(Data data_in)
{
    // リングバッファが確保されている場合のみ処理
    if (buffer_size_ > 0)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // 書き込みポインタを進める
        write_pointer_ ++;
        write_pointer_ %= buffer_size_;
        // データを書き込み
        data_[write_pointer_] = data_in;
        // 書き込んだデータ数を保持
        if (data_size_ < buffer_size_)
        {
            data_size_ ++;
        }
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Buffer is not allocated.\n");
    }
}

Data RingBuffer::readBuffer(uint read_pointer_in)
{
    // 過去にデータが書き込まれていないデータのポインタにはアクセスさせない
    if (read_pointer_in < data_size_)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // 現在の最新データのポインタを0, 古いほど値が大きくなる(最大: Buffer Size)
        uint read_pointer = (buffer_size_+write_pointer_-read_pointer_in)%buffer_size_;
        return data_[read_pointer] ;
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Read pointer is out of buffer. (%d / %d)\n",
                            read_pointer_in, data_size_);
        Data data;
        return data;
    }
}

// テストコード
int main()
{
    fprintf(stdout, "Error pattern -------\n"); 
    // 宣言(エラー)
    RingBuffer buf(0);
    Data data_1;
    data_1.number = 0;
    // データ書き込み(エラー)
    buf.writeBuffer(data_1);
    // データ読み込み(エラー)
    buf.readBuffer(1);
    // リングバッファのサイズを再設定
    buf.setBufferSize(10);
    // データ読み込み(エラー)
    buf.readBuffer(1);

    fprintf(stdout, "Normal pattern ------\n"); 
    for (uint i=0; i<2*buf.getBufferSize(); i++)
    {
        Data data_2;
        data_2.number = i;
        // データ書き込み(正常)
        buf.writeBuffer(data_2);
        fprintf(stdout, "%d / %d\n",i, 2*buf.getBufferSize()-1); 
        // 1. データ読み込み(最古のデータ 0 or 10)
        fprintf(stdout, "1. buf.data[%d].number: %d\n", 
            i%buf.getBufferSize(),
            buf.readBuffer(i%buf.getBufferSize()).number);
        // 2. データ読み込み(最新のデータ 0 to 19)
        fprintf(stdout, "2. buf.data[%d].number: %d\n", 
            0,buf.readBuffer(0).number);
    }
    return 0;
}

テンプレートのコンパイル方法

テンプレートのコンパイル方法を記載する。
$ g++ ./ring_buffer.cpp -std=c++11 -o ring_buffer_sample

テンプレートの実行結果

テンプレートの実行結果を記載する。
$ ./ring_buffer_sample
Error pattern -------
Error: [RingBuffer] Please set buffer size more than 0.
Error: [RingBuffer] Buffer is not allocated.
Error: [RingBuffer] Read pointer is out of buffer. (1 / 0)
Error: [RingBuffer] Read pointer is out of buffer. (1 / 0)
Normal pattern ------
0 / 19
1. buf.data[0].number: 0
2. buf.data[0].number: 0
1 / 19
1. buf.data[1].number: 0
2. buf.data[0].number: 1
2 / 19
1. buf.data[2].number: 0
2. buf.data[0].number: 2
3 / 19
1. buf.data[3].number: 0
2. buf.data[0].number: 3
4 / 19
1. buf.data[4].number: 0
2. buf.data[0].number: 4
5 / 19
1. buf.data[5].number: 0
2. buf.data[0].number: 5
6 / 19
1. buf.data[6].number: 0
2. buf.data[0].number: 6
7 / 19
1. buf.data[7].number: 0
2. buf.data[0].number: 7
8 / 19
1. buf.data[8].number: 0
2. buf.data[0].number: 8
9 / 19
1. buf.data[9].number: 0
2. buf.data[0].number: 9
10 / 19
1. buf.data[0].number: 10
2. buf.data[0].number: 10
11 / 19
1. buf.data[1].number: 10
2. buf.data[0].number: 11
12 / 19
1. buf.data[2].number: 10
2. buf.data[0].number: 12
13 / 19
1. buf.data[3].number: 10
2. buf.data[0].number: 13
14 / 19
1. buf.data[4].number: 10
2. buf.data[0].number: 14
15 / 19
1. buf.data[5].number: 10
2. buf.data[0].number: 15
16 / 19
1. buf.data[6].number: 10
2. buf.data[0].number: 16
17 / 19
1. buf.data[7].number: 10
2. buf.data[0].number: 17
18 / 19
1. buf.data[8].number: 10
2. buf.data[0].number: 18
19 / 19
1. buf.data[9].number: 10
2. buf.data[0].number: 19

備考


まとめ


  • C++でリングバッファを記述するテンプレートを記載した

参考文献



変更履歴


  1. 2019/08/28: 新規作成
  2. 2019/08/28: 備考追記
  3. 2019/09/02: 仕様追記

2019/08/26

forループをCUDAで並列化する方法

背景


CUDAを利用して並列化を行い、処理を高速化する必要が出てきたため、CUDAで並列化処理を記述する方法について記述する

記事の目的


CUDAで並列化処理を行う際のテンプレートを作成する

CUDA


ここでは、CUDAを利用したCプログラムの記述方法について記載する。

CUDAとは

CUDAは、nvidia社が提供するGPUを利用した並列演算プログラミング基盤である

利点

  • ユーザーが多く、情報も入手しやすい
  • ライブラリが充実している
  • 導入が容易である

テンプレート

CUDAを利用して並列処理を行うテンプレートを記載する。
// cuda_sample_code.cu
#include <stdio.h>

// デバイス(GPU)側のスレッドの設定 //////////////////////////////////
// BLOCK_SIZE_Xは、1ブロックあたりのスレッド数を表す
// GPUの種類により、1ブロックあたりのスレッド数の制限が異なる
// 最適なスレッド数を設定しないと、カーネル関数の処理がスキップされる
// 注)上記の場合、エラーでプロセスが落ちる事はない
#define BLOCK_SIZE_X 512

// カーネル関数(GPUで処理する関数)vec_sumの宣言 /////////////////////
// この関数がGPUで並列実行される
__global__
void vec_sum(float k, float *a, float *b, float *c)
{
    // iは、"for(int i=0; i < grid.x * block.x; i++)" の値を取る
    // ただし、並列処理のため、i++順に処理されるわけではない
    int i = blockIdx.x*blockDim.x + threadIdx.x;
    c[i] = k*a[i] + b[i];

    // GPU内のスレッドの同期 ////////////////////////////////////////
    // block_size * grid_size個の全てのスレッドが、ここで同期する
    __syncthreads();
}

// カーネル関数を呼び出す関数を宣言 /////////////////////////////////
void cuda_handler(float *a, float *b, float *c)
{
    // デバイス用のポインタを宣言 ///////////////////////////////////
    float *d_a, *d_b, *d_c;

    // デバイス(GPU)側の配列のメモリを確保 //////////////////////////
    // デバイス側のメモリ確保処理は重いため、回数は減らした方が良い
    cudaMalloc(&d_a, N*sizeof(float));
    cudaMalloc(&d_b, N*sizeof(float));
    cudaMalloc(&d_c, N*sizeof(float));

    // ホスト側の配列内のデータをデバイス側にコピー /////////////////
    cudaMemcpy(d_a, a, N*sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, b, N*sizeof(float), cudaMemcpyHostToDevice);

    // デバイス側の配列cの全ての要素に0をセット /////////////////////
    cudaMemset(d_c, 0, N*sizeof(float));

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、メモリへのデータセットのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());

    // 並列処理するスレッド数を定義 /////////////////////////////////
    // 総スレッド数は block_size * grid_size = N 個である
    // x, y, zの3次元まで設定可能である
    dim3 block_size (BLOCK_SIZE_X, 1, 1);
    dim3 grid_size  (N / block.x, 1, 1);

    // カーネル関数(GPUで処理する関数)の呼び出し ////////////////////
    // カーネル関数内では、デバイス側のメモリ内のデータのみ操作可能
    vec_sum<<<grid_size, block_size>>>(2.0f, d_a, d_b, d_c);

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、vec_sumのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());

    // この行までに実行されたカーネル関数の処理が完了するまで待機 ///
    // デバイスとホストの処理は非同期である
    // 同期処理を行うか、cudaMemcpyするまで、互いは独立して動作する
    cudaThreadSynchronize();

    // 計算結果をホスト側にコピー ///////////////////////////////////
    cudaMemcpy(c, d_c, N*sizeof(float), cudaMemcpyDeviceToHost);

    // デバイス(GPU)側の配列のメモリを開放 //////////////////////////
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、メモリへのデータセットのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());
}

int main(void)
{
    // 計算回数の設定 ///////////////////////////////////////////////
    // N = 512×2048
    int N = 1<<20;

    // ホスト用のポインタを宣言 /////////////////////////////////////
    float *a, *b, *c;

    // ホスト側の配列のメモリを確保 /////////////////////////////////
    a = (float*)malloc(N*sizeof(float));
    b = (float*)malloc(N*sizeof(float));
    c = (float*)malloc(N*sizeof(float));

    // a, bの配列にそれぞれ1,2を代入////////////////////////////////
    for (int i = 0; i < N; i++) {
        a[i] = 1.0f;
        b[i] = 2.0f;
    }

    // cudaでの処理を行う関数 ///////////////////////////////////////
    cuda_handler(a, b, c);

    // 計算結果の確認 ///////////////////////////////////////////////
    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
    {
        maxError = max(maxError, abs(c[i]-4.0f));
    }
    printf("Max error: %f", maxError);

    // ホスト側の配列のメモリを開放 /////////////////////////////////
    free(a);
    free(b);
    free(c);

    return 0;
}

テンプレートのコンパイル方法

テンプレートのコンパイル方法を記載する。
nvcc -lcuda ./cuda_sample_code.cu -o ./cuda_sample
デバッグログを出力する場合は、下記のように記載する。
nvcc -lcuda -g -G ./cuda_sample_code.cu -o ./cuda_sample

テンプレートの実行結果

テンプレートの実行結果を記載する。
$ ./cuda_sample
Max error: 0.000000

まとめ


  • CUDAでプログラミングをする際のテンプレートを調査、記載した

参考文献



変更履歴


  1. 2019/08/26: 新規作成

2019/08/25

make時にGPUのアーキテクチャ番号を自動設定させる方法

背景


複数種類の開発環境でまたがってCUDAを利用したプログラムを開発する際、マシンに搭載されたGPUのアーキテクチャに応じて、CMakeLists.txt内のアーキテクチャ番号を毎回書き換える必要があった。メンテナンスの効率化のため、どのGPU搭載のマシンでmakeした場合でも、GPUの種類を識別して、そのGPUに最適なバイナリファイルを生成する環境を構築する。

記事の目的


makeする際に自動で搭載GPUのアーキテクチャを識別し、そのGPUに最適なバイナリーを出力するようにmake環境を構築する

仮想/実アーキテクチャの設定


ここでは、UbuntuにおけるマルチGPU対応のmake環境を構築するための方法について記載する。

nvccのコンパイルオプション

nvccのコンパイルオプションのうち、アーキテクチャに関する部分について記述する。
  • --gpu-architecture (-arch)
  • *.cuが対象にしている仮想アーキテクチャを指定する。したがって、基本的にはcompute_*の中から選択する。
  • --gpu-code (-code)
  • 仮想アーキテクチャのコード(*.ptx)から生成して出力に加えるアーキテクチャを指定する。PTXを含めたいときはcompute_*、特定のGPU向けバイナリを含めたいときはsm_*を選択する。
  • --generate-code arch=compute_*,code=\"compute_*,sm_*\"
  • 上記2つのオプションを一度に設定できる

導入方法

make環境を構築する手順は、下記の通りである。
  1. check_cuda.cuをCMakeLists.txtと同じ階層に置く
  2. // check_cuda.cu
    #include <stdio.h>
    
    int main(int argc, char **argv){
        cudaDeviceProp dP;
        float min_cc = 3.0;
    
        int rc = cudaGetDeviceProperties(&dP, 0);
        if(rc != cudaSuccess) {
            cudaError_t error = cudaGetLastError();
            printf("CUDA error: %s", cudaGetErrorString(error));
            return rc; /* Failure */
        }
        if((dP.major+(dP.minor/10)) < min_cc) {
            printf("Min Compute Capability of %2.1f required:  %d.%d found",
                    min_cc, dP.major, dP.minor);
            printf(" Not Building CUDA Code");
            return 1; /* Failure */
        } else {
            printf("%d%d", dP.major, dP.minor);
            return 0; /* Success */
        }
    }
  3. CMakeLists.txtを編集する
  4. 下記のコードを追加することで、$CUDA_NVCC_FLAGSにコンパイルしたマシンのアーキテクチャを自動設定できる。
    cmake_minimum_required(VERSION 3.0)
    # Find CUDA
    find_package(CUDA)
    
    if (CUDA_FOUND)
      #Get CUDA compute capability
      set(OUTPUTFILE ${CMAKE_CURRENT_SOURCE_DIR}/cuda_script) # No suffix required
      set(CUDAFILE ${CMAKE_CURRENT_SOURCE_DIR}/check_cuda.cu)
      execute_process(COMMAND nvcc -lcuda ${CUDAFILE} -o ${OUTPUTFILE})
      execute_process(COMMAND ${OUTPUTFILE}
                      RESULT_VARIABLE CUDA_RETURN_CODE
                      OUTPUT_VARIABLE ARCH)
      execute_process(COMMAND rm ${OUTPUTFILE})
    
      if(${CUDA_RETURN_CODE} EQUAL 0)
        set(CUDA_SUCCESS "TRUE")
      else()
        set(CUDA_SUCCESS "FALSE")
      endif()
    
      if (${CUDA_SUCCESS})
        message(STATUS "CUDA Architecture: -arch=sm_${ARCH}")
        message(STATUS "CUDA Version: ${CUDA_VERSION_STRING}")
        message(STATUS "CUDA Path: ${CUDA_TOOLKIT_ROOT_DIR}")
        message(STATUS "CUDA Libararies: ${CUDA_LIBRARIES}")
        message(STATUS "CUDA Performance Primitives: ${CUDA_npp_LIBRARY}")
    
        set(CUDA_NVCC_FLAGS "{$CUDA_NVCC_FLAGS};--generate-code arch=compute_${ARCH},code=\"compute_${ARCH},sm_${ARCH}\"")
    
      else()
        message(WARNING -arch=sm_${ARCH})
      endif()
    endif()

まとめ


  • 搭載GPUのアーキテクチャを識別し、そのGPUに最適なバイナリーを出力するmake環境を構築する手順について調査、記載した

参考文献



変更履歴


  1. 2019/08/25: 新規作成

2019/08/24

CUDA10のコンパイラでrosのパッケージがコンパイルできない問題の対処法

背景


CUDA10のnvccを用いてrosのパッケージをコンパイルする際、エラーが発生してコンパイルできない問題が生じた。

記事の目的


CUDA10のnvccでrosパッケージをコンパイルできるようにする

nvccのバグ


ここでは、CUDA10のnvccでrosパッケージをコンパイルできない現象、原因及び対策について記載する。

現象

CUDA10のnvccでrosパッケージをコンパイルすると、下記のエラーが発生する場合がある。
$ catkin_make
...
/usr/include/pcl-1.7/pcl/point_cloud.h:586:100 error: template-id ‘getMapping’ used as a declarator
friend boost::shared_ptr& detail::getMapping(pcl::PointCloud &p);
^
/usr/include/pcl-1.7/pcl/point_cloud.h:586:100 error: ‘getMapping’ is neither function nor member function; cannot be declared friend
cc1plus: error: expected ‘;’ at end of member declaration
/usr/include/pcl-1.7/pcl/point_cloud.h:586:111: error: expected ‘)’ before ‘&’ token
...
make: *** [all] Error 2

原因

nvccが利用しているgnuコンパイラのバグが原因である。このバグは、「friend関数によりnamespace内で定義された関数を使用する」記述があるコードで発生する。ところで、rosは独自に手を加えたPointCloudLibrary(PCL)を使用しているが、PCLのコード内に上記の記述が存在する('/usr/include/pcl-1.7/pcl/point_cloud.h'内 l.586)。nvccのコンパイラは、上記箇所をコンパイルする際に、関数のnamespace部分を削除してしまう(detail::getMapping→getMapping)。そのため、コンパイラ自身が関数を見つけられなくなり、エラーとなる。
$less /usr/include/pcl-1.7/pcl/point_cloud.h 
'point_cloud.h'内の該当箇所
 
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
  } // namespace detail
 
    protected:
      // This is motivated by ROS integration. Users should not need to access mapping_.
      boost::shared_ptr<MsgFieldMap> mapping_;

      friend boost::shared_ptr<MsgFieldMap>& detail::getMapping<PointT>(pcl::PointCloud<PointT> &p);

    public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  };

  namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
    {
      return (p.mapping_);
    }
  } // namespace detail

対策

'/usr/include/pcl-1.7/pcl/point_cloud.h'を下記のように書き換えることで回避できる。
// Add ---------------------------------------------------------------------- //
template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
// -------------------------------------------------------------------------- //
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
  } // namespace detail
 
    protected:
      // This is motivated by ROS integration. Users should not need to access mapping_.
      boost::shared_ptr<MsgFieldMap> mapping_;
      // Change ------------------------------------------------------------------- //
      friend boost::shared_ptr<MsgFieldMap>& getMapping<PointT>(pcl::PointCloud<PointT> &p);
      // friend boost::shared_ptr<MsgFieldMap>& detail::getMapping<PointT>(pcl::PointCloud<PointT> &p);
      // -------------------------------------------------------------------------- //
    public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  };

// Add ---------------------------------------------------------------------- //
template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
{
    return (p.mapping_);
}
// -------------------------------------------------------------------------- //
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
    {
      return (p.mapping_);
    }
  } // namespace detail

備考

  • ros-kinetic、cuda 10.1で検証した
  • コンパイルが通ることと、実行可能であることを確認した

まとめ


  • rosをapt install時にインストールされるpclのヘッダーファイルを変更することにより、CUDA10のnvccでrosパッケージがコンパイルできなくなる問題を回避した

参考文献



変更履歴


  1. 2019/08/24: 新規作成

2019/08/20

Pyenvの導入(Ubuntu)

背景


Ubuntu開発用PCで、Python2、Python3、Anacondaを使用しているため、手軽に切り替えられる方法が必要だった。

記事の目的


pyenvを導入する

Pyenv


ここでは、Ubuntuにおけるpyenvの導入方法と使用方法について記載する。

Pyenvとは

Pyenvは、Pythonの複数のバージョンを使い分けるコマンドラインツールである。

利点

  • shellで書かれていてPython自体に依存しない
  • pyenv install <Version>のように打つだけで好きなバージョンがインストールでき、切り替えも容易にできる
  • virtualenvへのバインディングを標準で搭載し、バージョンを切り替えるのと同じインターフェースでパッケージ環境も切り替えられる

導入方法

UbuntuへのPyenvの導入手順は、下記の通りである。
  1. 必要ライブラリをインストールする
  2. $ sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
    libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
    xz-utils tk-dev libffi-dev liblzma-dev python-openssl git
  3. gitからPyenvのを取得する
  4. $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
  5. Pyenvにパスを通して有効化する
  6. $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
    $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
    $ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
    $ source ~/.bash_profile

使用方法

Pyenvの使用方法は、下記の通りである。
  • Pythonのインストール
  • $ pyenv install <Version>
  • Pythonのアンインストール
  • $ pyenv uninstall <Version>
  • インストールされたPythonのバージョン確認
  • $ pyenv versions
    * system
      <Version>
  • Pythonのバージョン切り替え(全体)
  • $ pyenv global <Version>
  • Pythonのバージョン切り替え(現在のディレクトリのみ)
  • $ pyenv local <Version>

アンインストール方法

Pyenvのアンインストール方法は、下記の通りである。
  1. pyenvのアンインストール
  2. $ rm -rf $(pyenv root)
  3. (オプション)bash_profileかzshrcにpyenv関連の情報がある場合、削除
  4. $ nano ~/.bash_profile
    もしくは、
    $ nano ~/.zshrc
    下記を削除
    export PYENV_ROOT="$HOME/.pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"
    eval "$(pyenv virtualenv-init -)"
  5. 再起動

まとめ


  • Pyenvの導入方法と使用方法について調査、記載した

参考文献



変更履歴


  1. 2019/08/20: 新規作成
  2. 2020/05/04: アンインストール方法追記

2019/08/17

PythonでSocketIO(クライアント編)

背景


PythonでSocketIO通信を行う必要があったため、今後の開発の効率化のためにSocketIO通信を行うクラスのテンプレートを作成する。

記事の目的


PythonでSocketIOクライアントを作成する

python-socketio


python-socketioを使用したSocketIOクライアントを作成する

選定理由

python-socketioの選定理由は、下記の通りである。

導入方法

python-socketioの導入手順は、下記の通りである。
  1. pipを利用して必要なライブラリをインストールする
  2. $ pip install requests python-socketio

テンプレート

python-socketioの使用テンプレートは、下記の通りである。
# socketioライブラリのインポート
import socketio
# ログ出力用ライブラリのインポート
from logging import getLogger, StreamHandler, DEBUG, INFO, ERROR
# 時間関係ライブラリのインポート
import time
# 終了シグナルをキャッチするライブラリのインポート
import signal
# マルチスレッドライブラリのインポート
import threading

# ロガーのインスタンス作成
logger         = getLogger(__name__)
stream_handler = StreamHandler()

# ログレベルを設定
log_level = INFO
logger.setLevel(log_level)
stream_handler.setLevel(log_level)

# ロガーにハンドラーをセット
logger.addHandler(stream_handler)

# SocketIOのテンプレート
class SocketIOClient:
    # namespaceの設定用クラス
    class NamespaceClass(socketio.ClientNamespace):
        def on_connect(self):
            pass
        def on_disconnect(self):
            pass
        def on_message(self, data):
            pass
        def on_server_to_client(self, data):
            pass
    # 接続時に呼ばれるイベント
    def on_connect(self):
        logger.info('Connected to server (%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        self.is_connect_ = True
    # 切断時に呼ばれるイベント
    def on_disconnect(self):
        logger.info('Disconnected from server (%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        self.is_connect_ = False
    def on_message(self, data):
        logger.info('Received message %s', str(data)) 
    # サーバーからイベント名「server_to_client」でデータがemitされた時に呼ばれる
    def on_server_to_client(self, data):
        logger.info('Received message %s', str(data)) 
    # Namespaceクラス内の各イベントをオーバーライド
    def overload_event(self):
        self.Namespace.on_connect          = self.on_connect
        self.Namespace.on_disconnect       = self.on_disconnect
        self.Namespace.on_message          = self.on_message
        self.Namespace.on_server_to_client = self.on_server_to_client
    # 初期化
    def __init__(self,ip,port,namespace,query):
        self.ip_         = ip
        self.port_       = port
        self.namespace_  = namespace
        self.query_      = query
        self.is_connect_ = False
        self.sio_        = socketio.Client()
        self.Namespace   = self.NamespaceClass(self.namespace_)
        self.overload_event()
        self.sio_.register_namespace(self.Namespace)
    # 接続確認
    def isConnect(self):
        return self.is_connect_
    # 接続
    def connect(self):
        # 接続先のURLとqueryの設定
        url = 'ws://'+self.ip_+':'+str(self.port_)+'?query='+self.query_
        logger.info('Try to connect to server(%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        try:
            self.sio_.connect(url, namespaces=self.namespace_)            
        except: 
            logger.error('Cannot connect to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else: 
            if not self.is_connect_:
                logger.error('Namespace may be invalid.(namespace="%s")',\
                              self.namespace_)   
    # 切断
    def disconnect(self):
        try:
            self.sio_.disconnect()
        except:
            logger.error('Cannot disconnect from server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            self.is_connect_ = False
    # 再接続
    def reconnect(self):
        self.disconnect()
        time.sleep(5)
        self.connect()
    # クライアントからデータ送信(send)する
    def sendData(self, data):
        try:
            self.sio_.send(data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            logger.info('Send message %s (namespace="%s")', str(data), self.namespace_) 
    # 独自定義のイベント名「client_to_server」で、クライアントからデータ送信(emit)する
    def emitData(self, data):
        try:
            self.sio_.emit('client_to_server', data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            logger.info('Emit message %s (namespace="%s")', \
                         str(data), self.namespace_)  
    # メインの処理
    def run(self):
        while True:
            self.connect()
            time.sleep(1)
            if self.is_connect_:
                break
        p = threading.Thread(target=self.sio_.wait)
        p.setDaemon(True)
        p.start()
    
if __name__ == '__main__':
    # Ctrl + C (SIGINT) で終了
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    # SocketIO Client インスタンスを生成
    sio_client = SocketIOClient('localhost', 10000, '/test', 'secret')
    # SocketIO Client インスタンスを実行
    sio_client.run()
    # データを送信
    for i in range(10):
        sio_client.sendData({'test_data': 'send_from_client'})
        sio_client.emitData({'test_data': 'emit_from_client'})
        time.sleep(1)
    # 切断
    sio_client.disconnect()
    logger.info('Finish')
    # 終了
    exit()

備考

  • namespaceは、必ず「/」(スラッシュ)から始まる必要がある
  • 上記テンプレートは、サーバーと通信テストができる

まとめ


  • Python-SocketIOを利用したクライアントモジュールを作成した

参考文献



変更履歴


  1. 2019/08/17: 新規作成

PythonでSocketIO(サーバー編)

背景


PythonでSocketIO通信を行う必要があったため、今後の開発の効率化のためにSocketIO通信を行うクラスのテンプレートを作成する。

記事の目的


PythonでSocketIOサーバーを作成する

python-socketio


python-socketioを使用したSocketIOサーバーを作成する

選定理由

python-socketioの選定理由は、下記の通りである。

導入方法

python-socketioの導入手順は、下記の通りである。
  1. pipを利用してインストールする
  2. $ pip install requests eventlet python-socketio

テンプレート

python-socketioの使用テンプレートは、下記の通りである。
# socketioライブラリのインポート
import socketio
# ログ出力用ライブラリのインポート
from logging import getLogger, StreamHandler, DEBUG, INFO, ERROR
# 時間関係ライブラリのインポート
import time
# 終了シグナルをキャッチするライブラリのインポート
import signal
# eventletライブラリのインポート
import eventlet
# マルチスレッドライブラリをインポート
import threading

# ロガーのインスタンス作成
logger         = getLogger(__name__)
stream_handler = StreamHandler()

# ログレベルを設定
log_level = INFO
logger.setLevel(log_level)
stream_handler.setLevel(log_level)

# ロガーにハンドラーをセット
logger.addHandler(stream_handler)

# SocketIOのテンプレート
class SocketIOServer:
    # namespaceの設定用クラス
    class NamespaceClass(socketio.Namespace):
        def on_connect(self, sid, environ):
            pass
        def on_disconnect(self, sid):
            pass
        def on_message(self, sid, data):
            pass
        def on_client_to_server(self, sid, data):
            pass
    # 接続時に呼ばれるイベント
    def on_connect(self, sid, environ):
        logger.info('Connected to client (sid: %s, environ: %s)',\
                     str(sid), str(environ))
        self.is_connect_ = True
    # 切断時に呼ばれるイベント
    def on_disconnect(self, sid):
        logger.info('Disconnected from client (sid: %s)',str(sid))
        self.is_connect_ = False
    # サーバーからデータ送信(send)する
    def send_data(self, data):
        try:
            self.sio_.send(data, namespace=self.namespace_) 
        except:
            logger.error('Has not connected to client')
        else:
            logger.info('Send message %s (namespace="%s")',\
                         str(data), self.namespace_) 
    # 独自定義のイベント名「server_to_client」で、サーバーからデータ送信(emit)する
    def emit_data(self, data):
        try:
            self.sio_.emit('server_to_client', data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to client')
        else:
            logger.info('Emit message %s (namespace="%s")',\
                         str(data), self.namespace_) 
    def on_message(self, sid, data):
        logger.info('Received message %s', str(data)) 
        self.send_data({'test_ack': 'send_from_server'})
    # クライアントからイベント名「on_client_to_server」でデータがemitされた時に呼ばれる
    def on_client_to_server(self, sid, data):
        logger.info('Received message %s', str(data))
        self.emit_data({'test_ack': 'emit_from_server'})
    # Namespaceクラス内の各イベントをオーバーライド
    def overload_event(self):
        self.Namespace.on_connect          = self.on_connect
        self.Namespace.on_disconnect       = self.on_disconnect
        self.Namespace.on_message          = self.on_message
        self.Namespace.on_client_to_server = self.on_client_to_server
    # 初期化
    def __init__(self,ip,port,namespace):
        self.ip_          = ip
        self.port_        = port
        self.namespace_   = namespace
        self.is_connect_  = False
        self.sio_         = socketio.Server(async_mode='eventlet')
        self.app_         = socketio.WSGIApp(self.sio_)
        self.Namespace    = self.NamespaceClass(self.namespace_)
        self.overload_event()
        self.sio_.register_namespace(self.Namespace)
    # 接続確認
    def isConnect(self):
        return self.is_connect_
    # 切断
    def disconnect(self,sid):
        try:
            self.sio_.disconnect(sid,namespace=self.namespace_)
        except:
            logger.error('Cannot disconnect from Client(sid="%s", namespace="%s")',\
                          namespace=self.namespace_)
        else:
            self.is_connect_ = False
    # 開始
    def start(self):
        try:
            logger.info('Start listening(%s:%d, namespace="%s")',\
                         self.ip_,self.port_,self.namespace_)
            eventlet.wsgi.server(eventlet.listen((self.ip_, self.port_)), self.app_)
        except: 
            logger.error('Cannot start listening(%s:%d, namespace="%s")',\
                          self.ip_,self.port_,self.namespace_)
    # メインの処理
    def run(self):
        p = threading.Thread(target=self.start)
        p.setDaemon(True)
        p.start()

if __name__ == '__main__':
    # Ctrl + C (SIGINT) で終了
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    # SocketIO Server インスタンスを生成
    sio_server = SocketIOServer('localhost', 10000, '/test')
    # SocketIO Server インスタンスを実行
    sio_server.run()
    # 接続待ち
    while not sio_server.isConnect():
        time.sleep(1)
    # 切断待ち
    while sio_server.isConnect():
        time.sleep(1)
    logger.info('Finish')
    # 終了
    exit()

備考

  • namespaceは、必ず「/」(スラッシュ)から始まる必要がある
  • eventletを使用した場合、他の作業スレッドから、SocketIO内のEmitやSendを呼び出せない
  • 上記テンプレートは、クライアントと通信テストができる

まとめ


  • Python-SocketIOを利用したサーバーモジュールを作成した

参考文献



変更履歴


  1. 2019/08/17: 新規作成

2019/08/15

クローンしたUbuntuをIntel NUC上で起動した際に、OSが起動しなくなる問題の対処法

背景


Intel NUCへUbuntuをクローンして同一環境のPCを増やした際に、"no bootable devices found"のメッセージが表示され、OSが起動しなくなった。

記事の目的


Intel NUC上で、クローンしたUbuntu起動できるようにする

フォールバック起動に変更


ここでは、起動しない原因と対処法について記載する。

起動しない原因

クローンしたUbuntuが起動しない原因は下記の通りである。
  • EFI下では、OSを起動するブートローダーにはUbuntuなどのOS固有の名称が付けられる
  • マシンがブートローダーを検索できるように、ブートローダーをファームウェアのNVRAMに登録する必要がある
  • 通常のインストール時には、NVRAMへの登録も自動的に行われる
  • クローンした場合はNVRAMへの登録が行われないため、ブートローダーが見つけられず、OSを起動できない

対処方法

EFI下では、ブートローダーが見つからない場合、フォールバックとして起動するブートローダー名が設定されている。
そこで、フォールバック用のブートローダーを作成する。
  1. メディアブート等でOSを起動する
  2. 対象のOSがインストールされたメディア(SSD、HDD)の中のEFI System Partition (ESP)をマウントする
  3. EFI/BOOTディレクトリ(フォールバック用のブートローダーディレクトリ)を作成する
  4. EFI/ubuntu/*を、EFI/BOOT/*へコピーする

対処方法例(Ubuntuメディアブートの場合)

Ubuntuメディアブートを行った場合の対処方法は、下記の通りである。
$ sudo fdisk -l
Device        Start      End  Sectors  Size Type
/dev/sda1      2048  1128447  1126400  550M EFI System
/dev/sda2   1128448 79626398 78497951 37.4G Linux filesystem
/dev/sda3  79628288 85917854  6289567    3G Linux swap
$ sudo mount /dev/sda1 /mnt #「Type」が「EFI System」のものをマウントする
$ cd /mnt
$ sudo mkdir EFI/BOOT #フォールバック用のブートローダーディレクトリ
$ sudo cp EFI/ubuntu/* EFI/BOOT/

まとめ


  • EFI下のPCでクローンしたUbuntuを使用する場合、OSが起動しなくなる問題の原因と対策について記載した

参考文献



変更履歴


  1. 2019/08/15: 新規作成

Blogger上でコードを埋め込む方法

背景


ソフトウェアエンジニアとして活動する中で、個人の備忘録をかねてBloggerで技術メモを作成することにした。記事内でコードを表示する場合があるため、Bloggerの記事でコードを埋め込む方法を調査した。

記事の目的


Bloggerの記事でコードを表示する

google-code-prettify


ここでは、技術メモ内におけるコードの埋め込み方法について記載する。技術メモでは、基本的にgoogle-code-prettifyでコードの表示を行う。

選定理由

google-code-prettifyの選定理由は、下記の通りである。
  • 複数の言語をサポートしている
  • 行番号を表示できる
  • PC表示時にスクロールバーを表示可能
  • 導入が容易

導入方法

google-code-prettifyの導入手順は、下記の通りである。
  1. Blogger管理画面を開く
  2. 「テーマ」 を選び、 「HTMLの編集」をクリックする
  3. 下記Javascriptコードを、html内の<head>...</head>に貼り付ける
  4. <!-- google-code-prettify -->
        <script src='https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?skin=sons-of-obsidian'/>
        <style type='text/css'>
            pre.prettyprint {
          overflow: auto;
            }
            .prettyprint ol.linenums &gt; li {
                list-style-type: decimal; 
            }
        </style>
    <!-- /google-code-prettify -->

使用方法

google-code-prettifyの使用方法は、下記の通りである。
<pre class="prettyprint linenums">
int sample_code(int b)
{
    int a = 10;
    a += b;
    return a;
}
</pre>
上記の通り記述すると、下記のように表示される。
int sample_code(int b)
{
    int a = 10;
    a += b;
    return a;
}

備考

  • 下記のように、言語を指定することも可能である。指定できる言語は、ここを参照。
  • <pre class="prettyprint lang-yaml linenums" >
    - name : tomato
      email: tomato@gmail.com
    - name : potato
      email: potato@gmail.com
    </pre>
  • 記事にコード埋め込む際は、HTMLに直接記述する。「作成」で記述した場合、正しく作成されない場合がある
  • <style type='text/css'>...</style>にスタイルに関する記述を追記することで、見た目の変更が可能である
  • Javascriptコードの4-6行目で、スクロールバー表示を設定している
  • Javascriptコードの7-9行目で、行番号を毎行表示に設定している

まとめ


  • 技術メモ作成にあたり、Bloggerにコードを表示させる方法を記述した
  • google-code-prettifyは、htmlの<head>...</head>間に、該当Javascriptコードを記入することで使用可能である

参考文献



変更履歴


  1. 2019/08/15: 新規作成
  2. 2019/08/23: 備考追記

Blogger上で数式を表示する方法

背景


ソフトウェアエンジニアとして活動する中で、個人の備忘録をかねてBloggerで技術メモを作成することにした。記事内で数式を扱う場合があるため、Bloggerの記事で数式を表示する方法を調査した。

記事の目的


Bloggerの記事で数式を表示する

MathJax


ここでは、技術メモ内における数式の作成方法について記載する。
技術メモでは、基本的にMathJaxで数式の作成を行う。

選定理由

MathJaxの選定理由は、下記の通りである。
  • Tex形式で数式を記述できる
  • ブログ記事内に直接数式を記述することで、数式が自動生成される

導入方法

MathJaxの導入手順は、下記の通りである。
  1. Blogger管理画面を開く
  2. 「テーマ」 を選び、 「HTMLの編集」をクリックする
  3. 下記Javascriptコードを、html内の<head>...</head>に貼り付ける
  4. <!-- MathJax -->
    <script 
      src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js'
      type='text/javascript'>    
      MathJax.Hub.Config({
        HTML: ['input/TeX','output/HTML-CSS'],
        TeX: { extensions: ['AMSmath.js','AMSsymbols.js'], 
          equationNumbers: { autoNumber: 'AMS' } },
          extensions: ['tex2jax.js'],
          jax: ['input/TeX','output/HTML-CSS'],
          tex2jax: { inlineMath: [ ['$','$'], ['\\(','\\)'] ],
          displayMath: [ ['$$','$$'], ['\\[','\\]'] ],
          processEscapes: true },
          'HTML-CSS': { availableFonts: ['TeX'],
          linebreaks: { automatic: true } }
      });
    </script>
    <!-- /MathJax -->

使用方法

MathJaxの使用方法は、下記の通りである。

  • インライン表示の場合
  • インライン表示: $e^{i\pi}=-1$
    
    インライン表示: $e^{i\pi}=-1$
  • 独立行表示の場合
  • $$\sum_{i=1}^{n}i = \frac{1}{2}n(n+1)$$
    
    $$\sum_{i=1}^{n}i = \frac{1}{2}n(n+1)$$

備考

  • MathJaxのJavascriptコードURL:(https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js)は将来的に変更される可能性がある。有効なURLはMathJaxのGetting Startに記載されている。
  • Broggerのテーマによっては、数式が表示されない。現状動作確認しているのは、「シンプル」のみである。
  • BroggerのテーマのHTMLを編集する際、「"」(ダブルクオート)を使用すると、文字化けし、正しく動作しない場合がある。 

まとめ


  • 技術メモ作成にあたり、Bloggerに数式を表示させる方法を記述した
  • MathJaxは、htmlの<head>...</head>間に、該当Javascriptコードを記入することで使用可能である

参考文献



変更履歴


  1. 2019/08/15: 新規作成

技術メモのドキュメントルール

背景


ソフトウェアエンジニアとして活動する中で、個人の備忘録をかねてBloggerで技術メモを作成することにした。読みやすい技術メモを作成するため、ドキュメントルールの作成を行った。

記事の目的


読みやすい技術メモ作成するため、技術メモのドキュメントルールを作成する

ドキュメントルール


ここでは、技術メモのドキュメントルールついて記載する。

記事のテーマ

  • 本ブログに記載するテーマは、IT関連のものに限定する。
  • 1つの記事に記載できるテーマは、1つに絞る。複数のテーマについて記載する場合は、記事を複数に分割した上、親となる記事を作成する。

記事の構成

  • 技術メモは、技術メモのテンプレート構成に従って作成する。
  • 「表示形式」は、「見出し」、「小見出し」、「標準」の3種類のみを使用する。
  • 読みやすくするため、極力箇条書きで記載する。

記事の内容

  • 最低限の情報のみを記載し、感想や所感は記載しない。

図の作成方法

  • 技術メモでは、基本的にPlantUMLで図の作成を行う。

数式の作成方法

  • 技術メモでは、基本的にMathJaxで数式の作成を行う。
\[ \sum_{i=1}^{n}i = \frac{1}{2}n(n+1) \]

コードの埋め込み方法

import numpy as np

a = 10

まとめ


  • 技術メモ作成にあたり、ドキュメントルールを作成した
  • 今後のブログ更新は、上記ルールに従って作成する

参考文献



変更履歴


  1. 2019/08/15: 新規作成

2019/08/14

技術メモのテンプレート

背景


ソフトウェアエンジニアとして活動する中で、個人の備忘録をかねてBloggerで技術メモを作成することにした。有効なメモを効率的に作成するため、テンプレートをまず作成する。

記事の目的


有効なメモを効率的に作成するため、技術メモのテンプレートを作成する

記事の内容


ここでは、技術メモのテンプレートに関する説明について記載する。

全体構成

技術メモは、「背景」、「記事の目的」、「記事の内容」、「まとめ」、「参考文献」、「変更履歴」の6項目から構成される。以下では、各項目の詳細について解説する。

  • 背景
  • 記事作成に至るまでの技術的な課題や背景について記載する。
  • 記事の目的
  • 記事の目指す目的について1行で記載する。
  • 記事の内容
  • 記事の目的に対する詳細な解説や手順について記載する。
  • まとめ
  • 記事の内容を3行以内で記載する。ただし、手順書に関してはまとめることが不可能なため、記載しない。
  • 参考文献
  • 記事内で参照した文献のリンクを記載する。
  • 変更履歴
  • 技術文書を書くための7つのルール」にも記載されているが、どんな人も、一回で完璧な文章を書くことはほぼ不可能である。記事は、内容が後で追加修正される可能性があるため、修正履歴を記載する。

図の作成方法

ここでは、技術メモ内における図の作成方法について記載する。
技術メモでは、基本的にPlantUMLで図の作成を行う。

PlantUMLを利用した図の作成方法の詳細は、別途記事を作成する。

数式の作成方法

ここでは、技術メモ内における数式の作成方法について記載する。
技術メモでは、基本的にMathJaxで数式の作成を行う。
\[ \sum_{i=1}^{n}i = \frac{1}{2}n(n+1) \]
MathJaxを利用した数式の作成方法の詳細は、別途記事を作成する。

コードの埋め込み方法

ここでは、技術メモ内におけるコードの埋め込み方法について記載する。
技術メモでは、基本的にGoogle-code-prettifyでコードの埋め込みを行う。
import numpy as np

a = 10
Google-code-prettifyを利用したコードの埋め込み方法の詳細は、別途記事を作成する。

まとめ


  • 技術メモ作成にあたり、テンプレートを作成した
  • 全体構成、図の作成方法に関して記述した

参考文献



変更履歴


  1. 2019/08/14: 新規作成
  2. 2019/08/15: 数式作成の記事へのリンク追加
  3. 2019/08/15: コード埋め込みの記事へのリンク追加



MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...