最近の投稿 Recent posts

基本情報技術者試験に合格しました

詳細

本日基本情報を受けてきました. CBTに移行してからはスコアレポートが受験後すぐに見れるようになっているのですが,午前・午後どちらも6割を超えており合格していると思います.

(2022/07/18追記) 合格証書がお家に送付されて来ました。合格のようです。

仕事が思ったより忙しくあまり勉強の時間が取れませんでしたが,受かってよかったです.

次は応用情報取得を目指して勉強していこうと思います.

DjangoでMarkdown形式のエディタを実装,HTML形式で表示する

詳細

Djangoでマークダウン形式のエディタを爆速で実装します.参考にしたサイトは以下です.

mdeditorとは

Markdown形式のエディタをDjangoに爆速で実装できるライブラリです.ここ 実際に動作しているところはリンク先をご覧ください.結構有能なのでびっくりします.

環境

  • Ubuntu18.04 LTS(さくらVPS)
  • Python3.6.8
  • Django==3.0
  • django-mdeditor==0.1.18
  • Markdown==3.3.4

mdeditorの実装

まず,pipで必要なライブラリを入れましょう

pip install django-mdeditor
pip install Markdown

mdeditor自体の設定をしていきます.settings.pyに以下を追記します.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'mdeditor' #これを追記
]
X_FRAME_OPTIONS = 'SAMEORIGIN'
MDEDITOR_CONFIGS = {
    'default': {
        'language': 'en',
    }
}

次に,models.pyを設定していきましょう.app内で定義するModelを以下のように定義します. 普段使っているmodels.TextFieldが Markdown形式のオブジェクトになります

#models.py
from django.db import models
from mdeditor.fields import MDTextField

# Create your models here.
class Article(models.Model):
    title = models.TextField(max_length=200)
    content = MDTextField() #ここがMarkdown形式のTextFieldになる

    def __str__(self):
        return self.title

また,{project_name}/urls.pyに以下を追記します.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('mdeditor/', include('mdeditor.urls')) #これを追記
]

admin.pyへの追記を忘れずに!

# admin.py
from django.contrib import admin
from .models import Article

# Register your models here.
admin.site.register(Article)

現時点でDjango Adminの管理画面からMarkdown形式で編集できるようになっているはずです.

Markdown_to_htmlの実装

次に,MarkdownをHTMLに変換していい感じに表示されるようにしていきましょう. 先ほど定義したArticleモデルに次を追記していきます.

#models.py
from django.db import models
from mdeditor.fields import MDTextField
import markdown #ここを追記

# Create your models here.
class Article(models.Model):
    title = models.TextField(max_length=200)
    content = MDTextField() #ここがMarkdown形式のTextFieldになる

    def __str__(self):
        return self.title

    # 以下を追記
    def markdown_to_html(self):
        md = markdown.Markdown(
            extensions=['extra', 'admonition', 'sane_lists', 'toc']
        )
        html = md.convert(self.content)
        return html

このメソッドをHTML内のDjangoテンプレートから呼び出していきます. 以下は関連する部分のHTMLコードです.

<!doctype html>
{% extends 'base.html' %}
{% load static %}

{% block content %}

{% for item in object_list %}
<article class="blog-post">
  <h4 class="blog-post-title">{{ item.title }}</h4>
  {{ item.markdown_to_html|safe }}
</article>
{% endfor %}
{% endblock %}

これだけだとコード整形のみが為されてお洒落なシンタックスハイライトがつきません. いい感じの見栄えにするために以下をHTML内に追記します.

<head>
  ...
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/a11y-dark.min.css">
  ...
</head>

<body>
  ...
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
  <script>hljs.initHighlightingOnLoad();</script>
</body>

これで完了です.試しにサーバーを立ち上げて確かめてみてください.

python3 manage.py runserver

pymodbusでModbus/TCPスレーブ(サーバー)を構築する

詳細

お久しぶりです。IMAXおじさんです。 今日は今までのWEB系の話とは少し変わって、PyModbusでModbus/TCPのサーバーを構築する話をします。

参考文献

HMSのサイト M-system技研のModbus/TCP通信仕様書

Modbus/TCPとは

Modbus/TCPとは、Modicon社(現Schneider Electric社)が1979年に策定した産業用Ethernetプロトコルです。 主にPLCやリモートIOとの通信に使用される産業用Ethernetプロトコルであり、世界的なシェアとしては5%程度を占めています(2020年現在)。

Ethernet系フィールドバスのシェア

通信の概要

サーバー(フィールド側,  PLCやリモートIO)
xxx.yyy.zzz.100
    |
    |
クライアント(PC)
xxx.yyy.zzz.101

Modbus/TCPはサーバー/クライアント方式の通信プロトコルです。 PLCやリモートIOなどがスレーブ(==サーバー)、データ収集を行うPCなどがマスター(==クライアント)としてデータのやり取りを行います。 名前の通り、各サーバーとクライアントがTCPでペイロードをやり取りします。 物理レイヤーの仕様(端子形状など)は指定されておらず、あくまで通信のレイヤーのみの仕様となります。

ペイロードの仕様に関しては、下記のM-system技研発行の通信仕様書がよくまとまっており参考になります。

M-system技研 Modbus通信仕様書

PyModbusとは

PyModbusとは上記のModbusサーバー・クライアントを擬似的にPythonで構築できるライブラリです。 対向機をわざわざ購入するとなると、数万円かかってしまいますが、PyModbusを使えば無料で手軽に対向機を用意できます。

PyPIのPyModbusページ

Modbus/TCPによる通信デモ

さて、Modbus/TCPの説明が終わったところで早速デモの構築に移りましょう。 PC上にPyModbusで構築したサーバーまでデータを取りに行くデモを構築します。

検証環境

  1. PC: DELL Inspiron 7370
  2. OS: Ubuntu20.04(LTS)
  3. Python: 3.8.10
  4. PyModbus: 2.5.3

Modbus/TCPスレーブ(サーバー)のPyModbusによる構築

PyModbusのサーバーを早速構築します。ローカルホストにサーバーを立てることにするため、特にIPアドレスは気にしなくてもよいです。 実際にデバイス間通信を試したい人は、Ethernetポートを備えたデバイスを2個用意して固定IPを振ってあげるとよいかと思います。

今回はinput_registerに値を入れることにします。 サーバーがレジスタ(input_register)に保持する値は、以下の通りとしましょう。

----------------------------
register1 |   int16
----------------------------
register2 |   float
register3 |   (32bit)
----------------------------

Modbusでは、1レジスタ=2byte(16bit)で扱われることに注意してください。なので、32bitのfloatなど16bitを超えるようなデータはレジスタをまたがって保持されることになります。

さて、このような値を返してくれるサーバーをPyModbusで構築していきます。まずは必要ライブラリのimportを。

# sample_server.py
from pymodbus.constants import Endian
from pymodbus.version import version
from pymodbus.server.asynchronous import StartTcpServer

from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.payload import BinaryPayloadBuilder

サーバー側でアクセス・送受信しているパケットの内容を確認するため、ログの設定をします。

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

次に、レジスタに入れる値を作っていきます。 PyModbusではpymodbus.BinaryPayloadBuilderを使ってペイロードのオブジェクトを作ることになります。

builder = BinaryPayloadBuilder(byteorder=Endian.Big) #ペイロードビルダーのインスタンスを作成。バイトオーダーは再現したいリモートIOの仕様に準ずること。

# 最初のint_16のペイロードを作成
builder.add_16bit_int(100)

# 2つ目のfloatのペイロードを作成
builder.add_32bit_float(123.45)

# ペイロードをModbusSequentialDataBlockへ変換
block = ModbusSequentialDataBlock(1, builder.to_registers())

# レジスタへの登録を行う
store = ModbusSlaveContext(ir=block, zero_mode=False)
context = ModbusServerContext(slaves=store, single=True)

最後に、サーバーを起動しておしまいです。

StartTcpServer(context, address=("0.0.0.0, 502")) # ローカルホストで起動

Modbus/TCPマスター(クライアント)のPyModbusによる構築

サーバーの構築はできたので、サーバーにためている情報を取りに行くクライアントを作ります。

Modbusではクライアントからのみ通信を始めることができます。 クライアントから打てるリクエストにはいくつか種類(ファンクションコード)があります。 詳しくはModbus/TCPの通信仕様書を見ていただきたいのですが、今回はファンクションコード4(FC4)を使います。 FC4はread_input_registersというリクエストになっており、その名の通りinput_registersの値を読み取るものになります。

(余談ですが、Modbus/TCPではread_input_registersで読まれるレジスタアドレスは300001から始まると決まっているようです。FC4なのに開始アドレスが3xxxなのは気持ちが悪い...)

サーバーの設定では、レジスタ3つにまたがってデータが溜まっているのでread_input_registersで3つ分レジスタを読んであげましょう。

from pymodbus.client.sync import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

# Modbusクライアントのインスタンス。ローカルホストにサーバーが立っているので、アクセス先をlocalhostと指定。
client = ModbusTcpClient("localhost", port=502)

# FC4のread_input_registersでレジスタアドレス300001+0 ~ 300001+2を読み取る。
# 第一引数が開始アドレス(相対)、第二引数がレジスタ数
res = client.read_input_registers(0, 3, unit=1)

# 読み取ったレジスタ値をデコーダインスタンスへ渡す
# サーバー側がビッグエンディアン設定なので、こちらも合わせる。
decoder = BinaryPayloadDecoder.fromRegisters(res.registers, byteorder=Endian.Big)

# 最初のint_16bitをデコード
int16 = decoder.decode_16bit_int()

# 次のfloat_32bitをデコード
float32 = decoder.decode_32bit_float()

print(int16) # -> 100
print(float32) # -> 123.45...

これで、サーバーのinput_registersに溜まったデータをModbus/TCPで読み取ることができました。

まとめ

産業用ネットワーク難しい

nginx + uwsgi + django on dockerでサイトを公開する

詳細

nginx + uwsgi + django on dockerでサイトを構築し,localhost上で公開するまでの解説です.

webサーバーの動き方に関して

djangoにはpython3 manage.py runserverで起動できる便利な開発サーバー機能が備わっています. しかし,本番環境ではこの機能は使わずに,apacheやnginxなどのwebサーバーを使用することが推奨されています. 今回は,nginxを使用してdjangoアプリケーションデプロイすることにします. また,環境の移植性を考慮して,dockerを使用したデプロイを行います.

nginx + uwsgi + djangoの具体的な処理の流れはこちらで解説されていますので,ぜひ参照してください. 非常に分かりやすく書かれており,これを読むだけである程度の設定はできてしまいます.

Setting up Django and your web server with uWSGI and nginx

簡単に図解すると,下の図のようにリクエストが流れていくことになります.

webクライアント -> nginx(80,443ポート) -> uwsgi(8000ポート) -> djangoバックエンド
  1. nginxがwebサーバーとして機能しwebクライアントからリクエストを受け取る.
  2. uwsgiがアプリケーションサーバーとしてnginxからのリクエストを受け取る.
  3. uwsgiがdjangoを動かし,結果のレスポンスを返す

この構成を実現するため,以下ではnginx, uwsgi, djangoの各項目の設定事項を見ていきます.

docker-compose.ymlの中身

まず,全体の見通しをよくするために,docker-compose.ymlの中身を紹介しておきます.

version: "3"

services:
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./django-project/myapp/static:/static
      - ./django-project/myapp/media:/media
    ports:
      - 8000:80
    environment:
      - LANG=C.UTF-8
      - LANGUAGE=en_US
    depends_on:
      - django-project
      - mysql

  django-project:
    build: ./django-project
    restart: always
    command: bash /init.sh
    tty: true
    volumes:
      - ./django-project/app:/myapp
      - ./django-project/init.sh:/init.sh
    environment:
      - DEBUG=False
      - LOCALHOST=localhost
      - DEPLOYHOST=#デプロイ先のドメイン名
    depends_on:
      - mysql
      - redis-server

  mysql:
    # mysqlに関する記述
    ....

各ファイルのフォルダ構成

project/
|__django-project/
|    |__myapp/
|    |    |__myapp/
|    |    |__static/
|    |    |__media/
|    |    |__manage.py
|    |__Dockerfile
|__nginx
|    |__conf/nginx.conf
|    |__uwsgi_params
|__mysql
|__docker-compose.yml

nginxの設定内容

まずnginxの設定を行います. ホストOS側で./nginx/conf/nginx.confを作成し,に以下の項目を書き足していきます.

upstream django {
    ip_hash;
    server django-project:8000;
}

server {
    listen 80;
    charset utf-8;

    client_max_body_size 75M;

    location /static {
        alias /static;
    }

    location /media {
        alias /media;
    }

    location / {
        uwsgi_pass django;
        include /etc/nginx/uwsgi_params;
    }
}

server_tokens off;

上記のポイントは,以下です.

  1. upstreamでuwsgiサーバーを示すホスト名:ポート名を指定
  2. listen 80で80番ポートをリッスン
  3. location /static, location /mediaで静的ファイルの場所を指定.docker-compose.yml内でマウントした/static, /media内のファイルを探します.
  4. location /でuwsgiをリクエストを流す先として指定します.uwsgi_paramsここからコピペで問題ありません.

uwsgiの設定内容

uwsgiはdjangoと同じdockerイメージの中に入れます../django-project/Dockerfile内に以下の内容を書き加えます.

FROM python:3.8-slim-buster

# djangoなど必要なライブラリをインストール
COPY ./requirements.txt /requirements.txt

# RUN source /venv/bin/activate
RUN python3.8 -m pip install --upgrade pip
RUN python3.8 -m pip install -r requirements.txt
RUN python3.8 -m pip install uwsgi # uwsgiを入れること

これでdjangoをuwsgiで動かす準備はできました. 次にuwsgiでリクエストを待ち受けるために,コンテナ起動時に実行するシェルスクリプトを書いていきます.

#!/bin/bash

cd /myapp;
bash -c "python3 manage.py makemigrations";
bash -c "python3 manage.py migrate";
bash -c "python3 manage.py collectstatic --noinput"
bash -c "uwsgi --socket :8000 --module myapp.wsgi --py-autoreload 1s";

uwsgi(8000番ポート)で待ち受けることを最後の行で書き加えています.

djangoの設定内容

最後に,djangoの設定を行なっていきます. 変更が必要なのは,/myapp/myapp/settings.pyのみです.

  1. デバッグモードのON/OFFをdocker-compose.ymlから指定できるようにする. docker-compose.ymlenvironmentDEBUG=True/Falseと指定することでデバッグモードが変えられるように,環境変数の読み込み部分を追記します. また,djangoはDEBUG=Falseの際には外部からの通信を受け付けないので,ALLOWED_HOSTSにも環境変数の読み込みを追加します. DEPLOYHOSTの部分にデプロイするサーバーのホスト名を書きます.
# settings.py
# SECURITY WARNING: don't run with debug turned on in production!
is_debug_mode = os.environ.get("DEBUG")
DEBUG = (is_debug_mode == "True")
ALLOWED_HOSTS = [os.environ.get("LOCALHOST"), os.environ.get("DEPLOYHOST")]
  1. nginxが静的ファイルをサーブできるように,staticファイルのルートを指定する. STATIC_ROOTMEDIA_ROOTを指定することで,DEBUG=Falseの際に静的ファイルはnginxが直接サーブするようにできます.
# settings.py
STATIC_URL = '/static/'
STATICFILE_DIRS = [
    os.path.join(BASE_DIR, 'userctrl/static/'),
    os.path.join(BASE_DIR, 'contest_manage/static/'),
    os.path.join(BASE_DIR, 'ace-builds')
]
STATIC_ROOT = os.path.join(BASE_DIR, "static")

MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"

【Docker】alpineイメージをproxy環境下で使用する場合

詳細

alpine linuxイメージをプロキシ環境下で使用する場合、様々な障壁に阻まれてコケることがあります。 以下では、その回避策を列挙していきます。(問題の切り分けができていないので一部おかしいことを言っているかもしれません)

1. プロキシの環境変数への登録

Dockerfile内でプロキシを登録します。 ここでRUN export http_proxy={プロキシ:ポート}とすると、なぜか環境変数に反映されておらずハマりました。 解決策としては

ENV http_proxy=http://{プロキシのIPアドレス}:{ポート}/
ENV https_proxy=http://{プロキシのIPアドレス}:{ポート}/

とすることで環境変数に反映することができました。

2. レポジトリの追加

プロキシを経由する関係でapkレポジトリからライブラリを引っ張ってこれない場合があります。 これは、デフォルトでapkがインデックスを探しに行くレポジトリURLがhttps://~となっている場合に起こります。 下記のissueにはdl-cdn.alpinelinux.org does not support TLS at all. (中略) APK repositories should be http onlyとあります。 そのため、/etc/apk/repositoriesにプレーンなhttpのURLの記述とする必要があります。

Githubのissue

すなわち、

RUN rm /etc/apk/repositories
RUN echo "http://cd-cdn.alpinelinux.org/alpine/v3.13/main" >> /etc/apk/repositories
RUN echo "http://cd-cdn.alpinelinux.org/alpine/v3.13/community" >> /etc/apk/repositories

とし、httpでapkのレポを探しに行きます。

ページ 3 / 4 (全 17 件)

About

IMAXおじさんが(主に)技術系記事を備忘録として残していくブログです.

Category

  1. Tech
  2. Daily
  3. Job
  4. Other