温度、湿度、気圧、明るさを Raspberry Pi に接続したグラフィックLCDに表示

●概要

Raspberry Pi で温度や湿度、気圧などを測定して表示するアプリケーションを紹介したいと思います。ハードウエアは市販のボードを使用しますので簡単に作成することができます。今回はスイッチサイエンス社が販売している、“Raspberry Pi用環境センサ基板” を使用しました。

下記は、環境センサ基板にオプションのグラフィック LCD モジュールを接続したものを Raspberry Pi ver3 で動作させている様子です。

ボードには予め BME280 センサと、I2Cでアクセスできる光センサーが搭載されていますので簡単に Raspberry Pi から測定値を取得することができます。また、グラフィック LCD を接続して最新の測定値をリアルタイムに表示することができます。

今回作成したアプリケーションはグラフィックLCDに温度や湿度、気圧をリアルタイムに表示します。上記の画面の他にも、光センサ表示、デジタル時計表示、IP アドレス表示の各ページを環境センサボード上のスイッチ SW1 で切り替えることができます。

今回のアプリケーションは Raspberry Pi 本体と 環境センサ基板(+ グラフィックLCDモジュール)だけで動作しますので簡単に作成することができます、是非お試しください。

●ハードウエア構成

今回のアプリケーションを動作させるために必要な機材は以下になります。このほかにも、Raspberry Pi 本体用の電源やネットワークケーブル、コンソール端末(インストールやセットアップ作業時に使用)

Raspberry Pi 本体 (B+/2/3) x1

スイッチサイエンス社製 環境センサ基板  x1

環境センサ基板にオプションで接続する AQM1248A小型グラフィック液晶ボード x1

環境センサ基板に LCD モジュールを接続するためのピンヘッダ(両端オス) x1

●ソフトウエア構成

Raspberry Pi に接続する環境センサ(BME280等)は I2C バスで接続されていて、 LCD グラフィックディスプレイは SPI インターフェイスで接続されています。これらのハードウエアインターフェイスを操作したり、表示プログラムの Lua スクリプトの実行環境のためにオールブルーシステムの abs_agent プログラムを使用します。abs_agent のインストールキットと詳しいマニュアルは、こちらから Raspberry Pi 用のバイナリアーカイブをダウンロードできます。個人目的であればフリー版ライセンスが同梱されていますので、期間の制限なく直ぐに使用するこができます。今回紹介するスクリプトもインストールキットに最初から含まれていますので、一部コメントを削除するだけで簡単にセットアップできます。

●インストール

最初に、Raspberry Pi には標準 OS の Raspbian の最新バージョンをインストールしておきます。

公開されている Python や C で作成したプログラムからRaspberry Pi のI2C や SPI を使用する場合には、Raspbian のコンフィギュレーションをデフォルト値から変更する必要がある場合が多いのですが、abs_agent を使用する場合にはこれらの作業は必要ありませんので省略できます。(もちろん、I2C や SPI コンフィギュレーションを変更してカーネルドライバ追加や専用のデバイスファイルを作成した状態でも abs_agent は問題なく動作します)

オールブルーシステムのダウンロードページから最新の abs_agent のインストールキットをダウンロードしてください。このとき、Intel x86 タイプとRaspberry Pi 用の2種類のバイナリがありますので、間違えずに Raspberry Pi 用のものを選択してください。また、インストール手順の詳細や Lua ライブラリ関数の使用方法を確認するために、abs_agent ユーザーマニュアルもダウンロードしておくと便利です。

Raspberry Pi にデフォルトユーザー名 “pi” でログインして、ダウンロードしたインストールキットファイルをホームディレクトリに配置します。その後、”tar zxvf <インストールキットファイル名>” のコマンドを実行して、abs_agent プログラム一式を /home/pi/abs_agent ディレクトリの下にインストールします。(下記実行例を参照してください)

●サーバー起動時に実行される SERVER_START イベントハンドラを修正

一般的なアプリケーション作成時には、先に abs_agent を起動させた状態でアプリケーションを構築してしていくのですが、今回は既にセンサ情報表示プログラムのスクリプトが作成済みですので、それらの自動起動設定を先に行います。

abs_agent 起動時には SERVER_START.lua スクリプトが自動で一度だけ実行されて、このスクリプト中にハードウエアやアプリケーションの初期化を作業を記述することができるようになっています。

今回は環境センサ基板に搭載されている3つのタクトスイッチと 各種LED が接続されている GPIO ポートの初期化を記述します。インストール直後の SERVER_START.lua スクリプト中には既に環境センサ基盤用の初期化がコメントとして記述されています。ここではこのコメントを外して環境センサ基板の設定を有効にするだけです。

abs_agent をインストールしたディレクトリ (/home/pi/abs_agent) の中の scripts ディレクトリに移動して、SERVER_START.lua ファイルを vi エディタで編集します。

スクリプトファイルの最後の部分に記述されている環境センサ基板用の設定部分のコメントを外して有効にします。lua のコメントは “–” ハイフン2つを続けて行全体をコメントにするのと、”–[[" と "]]” で囲まれた複数行をコメントにすることができます。ここでは “–[[" と "]]” を削除して環境センサ基板の設定全体を有効にしてください。

コメントを外して、設定を有効にした部分は以下の様になります。

-------------------------------------------------------------------
-- スイッチサイエンス社製 環境センサボード(RPi_EnvSensor_Rev4) 設定
-------------------------------------------------------------------

raspi_gpio_config(18,"input","off")		-- SW3
raspi_gpio_config(23,"input","off")		-- SW2
raspi_gpio_config(24,"input","off")		-- SW1

raspi_gpio_config(4,"output","off")		-- LED4 Ir
raspi_gpio_config(17,"output","off")	-- LED3 BLUE
raspi_gpio_config(22,"output","off")	-- LED1 RED
raspi_gpio_config(25,"output","off")	-- LCD RS (AQM1284A)
raspi_gpio_config(27,"output","off")	-- LED2 GREEN

-- GPIO 初期値出力、全LED消灯, LCD RS->low
raspi_gpio_write(4,false)
raspi_gpio_write(17,true)
raspi_gpio_write(22,true)
raspi_gpio_write(25,false)
raspi_gpio_write(27,true)

-- アラームタスク起動
script_fork_exec("RASPI/ENVSENSOR_ALARM_TASK","","")

-- センサー値表示タスク起動
script_fork_exec("RASPI/ENVSENSOR_DISPLAY_TASK","","")

最初にタクトスイッチが接続されている GPIO ポートを入力モードにしています。スイッチサイエンス社のホームページではこの基板の回路図が公開されていますので参照します。これによると、タクトスイッチ周りの外付けのプルアップ抵抗が既に接続済みなので、ここでは Raspberry Pi 内部のプルアップ設定は “off” にします。

また各種LED 用に GPIO ポートを出力に設定します。その後 LED を消灯状態にするために初期値を設定します。このとき、赤・青・緑 LED は GPIO Low 出力時に点灯する仕様なのでそのように初期設定します。

GPIO 初期設定後に、今回のアプリケーションのメインタスクを記述している ENVSENSOR_DISPLAY_TASK スクリプトを起動します。

このスクリプトは起動後、無限ループに入ってセンサ値取得とLCD表示を繰り返します。このため、起動時には別スレッドで起動させる script_fork_exec() ライブラリ関数を使用しています。このスクリプトについての詳しい説明は後述します。

この他にも、ENVSENSOR_ALARM_TASK スクリプトを起動していますが、今回の記事ではこのアプリケーションは使用していませんので “–” を挿入してコメントアウトしても構いません。

このENVSENSOR_ALARM_TASK スクリプトは、環境センサ基板上の3つの LED を他のアプリケーションや Web API, MQTT ブローカ経由で操作できるように作成しています。例えば、グローバル共有変数名 “alarm_blue” に “blink” 文字列を設定すると青色 LED が点滅します。興味がありましたら、スクリプト中のコメントに仕様等が記述されていますのでご覧ください。

●タクトスイッチ操作時に実行される RASPI_CHANGE_DETECT イベントハンドラを修正

今回のアプリケーションはグラフィック LCD ディスプレイにデフォルトで気温や湿度、気圧などを表示しますが、その他にも時計表示や IP アドレスを表示する機能があります。これらのページ切り替えには環境センサ基板上の SW1 タクトスイッチを使用します。

タクトスイッチは前述の SERVER_START イベントハンドラ中で入力モードに設定されています。また、メインタスクの ENVSENSOR_DISPLAY_TASK スクリプト起動時に GPIO 入力値が変化したときにイベントを検出するモードに設定します(詳しくは後述します)。

スイッチを操作イベントが発生したときに実行される RASPI_CHANGE_DETECT イベントハンドラの内容を、インストール時のデフォルト設定から変更します。

以下の様に RASPI_CHANGE_DETECT.lua ファイルを vi エディタで編集します。

スクリプトファイルの最後の部分に記述されている環境センサ基板用の設定部分のコメントを外して有効にします。

コメントを外して、設定を有効にした部分は以下の様になります。

---------------------------------------------------------------------------------------
-- スイッチサイエンス社製 環境センサーボード用のセンサー値表示アプリケーション設定
-- アプリケーションの詳細は RASPI/ENVSENSOR_DISPLAY_TASK スクリプトを参照してください
-- アプリ中で指定したページ切り替えを行うスイッチ(SW1)の入力を検出して、
-- アクティブページを示すグローバル共有変数をインクリメントする
---------------------------------------------------------------------------------------
if change_bit[24] then -- 環境センサーボードの SW1 を操作した?
	if change_bit[24] == 0 then
		local stat,val = inc_shared_data("ENVSENSOR_ACTIVE_PAGE")
		if tonumber(val) > 4 then -- SWを押すたびにアクティブなページ番号を 1..4 の順に設定する
			set_shared_data("ENVSENSOR_ACTIVE_PAGE","1")
		end
	end
end

最初に SW1 が接続されている GPIO#24 ピンが変化していたかどうかをチェックします。その後、SW1 スイッチを押し込んだ状態の場合だけを判断して、アプリケーションで表示するページ番号をインクリメントします。

表示中のアクティブなページ番号はグローバル共有変数(キー名: ENVSENSOR_ACTIVE_PAGE)に格納されていて、この値を変更すると起動中のアプリケーションの表示画面をリアルタイムに変更させることができます。また、ページは現在4つまでしか用意していないので、ページ番号が “4″ を超えたらになったら “1″ に戻しています。

●アプリケーション起動

ここまでの設定作業で、abs_agent を起動すると同時にアプリケーションも自動的に実行されるようなりました。早速 abs_agent を起動させます。

上記のコマンド実行例ではログサーバーにメッセージを出力するように、”-l <ログサーバーのIPアドレス>” もオプションで指定していますがこの指定は省略しても構いません。

ログサーバーを設置すると abs_agent 動作の詳細を Windows PC から確認することができます。詳しくは abs_agent ユーザーマニュアルをご覧ください。ログサーバーを設置していると下記の様な起動メッセージが出力されます。

abs_agent が起動すると同時に、アプリケーション・メインタスク用の “RASPI/ENVSENSOR_DISPLAY_TASK” Lua スクリプトが実行されて環境センサ基板のグラフィック LCD に現在の気温、湿度、気圧が表示されます。

表示は約 1秒ごとに更新されて現在の測定値がリアルタイムに表示されます。ここで SW1 ボタンを押すと明るさの測定値画面に切り替わります。

光センサに手をかざすと測定値とバーグラフがリアルタイムに変化するのを確認できると思います。もう一度 SW1 を押すと時計表示になります。

もう一度 SW1 を押すと、Raspberry Pi の IP アドレスと現在時刻を表示します。

以降 SW1 を押すごとに上記のページを繰り返し切り替えて表示します。

●センサ値の取得スクリプト説明と手動実行

ここからはアプリケーション内部で実行しているスクリプトの詳細を説明します。表示アプリからは環境センサ基板に搭載されている BME280 センサから1秒に一回、気温、湿度、気圧データを取得しています。この機能を実現しているスクリプト(RASPI/DEVICE/BME280_READ) の内容は下記の様になっています。

--[[

●機能概要

I2C バスに接続した気圧・温度・湿度センサー(BME280) の値を取得する

●リクエストパラメータ

---------------------------------------------------------------------------------
キー値          値                                               値の例
---------------------------------------------------------------------------------
bus             I2C バス番号                                     "1"
                "0" または "1"を指定、省略時は "1" を使用する

init			このパラメータを指定した場合には、強制的にデバイス
				初期化と補償データレジスタの取得を行う。
				パラメータ値は任意。		 					 "1"

●リターンパラメータ

---------------------------------------------------------------------------------
キー値          値                                               値の例
---------------------------------------------------------------------------------

temperature     センサーから取得した摂氏温度                     "12.50"
                                                                 "-25.00"

humidity		センサーから取得した相対湿度(%)                  "75.00"

pressure	    センサーから取得した気圧(hPa)	                 "1013.10"

]]

local slave_addr = "76"
local bus = 1
local stat,data,result

-------------------------
-- パラメータチェック
-------------------------
if g_params["bus"] then
    bus = tonumber(g_params["bus"])
end

-----------------------------------------------------------------------
-- 初期化と補償データレジスタの取得
-----------------------------------------------------------------------
stat,data = get_shared_data("BME280_REGVAL")

if (data == "") or g_params["init"] then
	log_msg("initialize and setup the device",g_script)

	-----------------------------------------------------------------------
	-- BME280 の測定パラメータ設定
	-----------------------------------------------------------------------
	local osrs_t = 1			-- Temperature oversampling x 1
	local osrs_p = 1			-- Pressure oversampling x 1
	local osrs_h = 1			-- Humidity oversampling x 1
	local mode   = 3			-- Normal mode
	local t_sb   = 5			-- Tstandby 1000ms
	local filter = 0			-- Filter off
	local spi3w_en = 0			-- wire SPI Disable

	local ctrl_meas_reg = bit_or(bit_lshift(osrs_t,5),bit_lshift(osrs_p,2),mode)
	local config_reg    = bit_or(bit_lshift(t_sb,5),bit_lshift(filter,2),spi3w_en)
	local ctrl_hum_reg  = osrs_h

	if not raspi_i2c_write(bus,slave_addr,"F2" .. bit_tohex(ctrl_hum_reg,2)) then error() end
	if not raspi_i2c_write(bus,slave_addr,"F4" .. bit_tohex(ctrl_meas_reg,2)) then error() end
	if not raspi_i2c_write(bus,slave_addr,"F5" .. bit_tohex(config_reg,2)) then error() end

	---------------------------------------------------------------------------
	-- BME280 補償データレジスタの値を取得後共有データに保存
	-- 次回からのスクリプト実行時には共有データに保存されたレジスタ値を使用
	---------------------------------------------------------------------------
	stat = raspi_i2c_write(bus,slave_addr,"88")
	if not stat then error() end
	stat,result = raspi_i2c_read(bus,slave_addr,24)
	if not stat then error() end
	data = result

	stat,result = raspi_i2c_write(bus,slave_addr,"A1",1)
	if not stat then error() end
	data = data .. result

	stat,result = raspi_i2c_write(bus,slave_addr,"E1",7)
	if not stat then error() end
	data = data .. result

	set_shared_data("BME280_REGVAL",data)

end

------------------------------------
-- 補償パラメータ計算
------------------------------------
local reg = hex_to_tbl(data)
local dig_T1 = reg[1] + bit_lshift(reg[2],8)
local dig_T2 = bit_tosigned(reg[3] + bit_lshift(reg[4],8),16)
local dig_T3 = bit_tosigned(reg[5] + bit_lshift(reg[6],8),16)
local dig_P1 = reg[7] + bit_lshift(reg[8],8)
local dig_P2 = bit_tosigned(reg[9] + bit_lshift(reg[10],8),16)
local dig_P3 = bit_tosigned(reg[11] + bit_lshift(reg[12],8),16)
local dig_P4 = bit_tosigned(reg[13] + bit_lshift(reg[14],8),16)
local dig_P5 = bit_tosigned(reg[15] + bit_lshift(reg[16],8),16)
local dig_P6 = bit_tosigned(reg[17] + bit_lshift(reg[18],8),16)
local dig_P7 = bit_tosigned(reg[19] + bit_lshift(reg[20],8),16)
local dig_P8 = bit_tosigned(reg[21] + bit_lshift(reg[22],8),16)
local dig_P9 = bit_tosigned(reg[23] + bit_lshift(reg[24],8),16)
local dig_H1 = reg[25]
local dig_H2 = bit_tosigned(reg[26] + bit_lshift(reg[27],8),16)
local dig_H3 = reg[28]
local dig_H4 = bit_tosigned(bit_and(reg[30],0x0f) + bit_lshift(reg[29],4),16)
local dig_H5 = bit_tosigned(bit_lshift(reg[31],4) + bit_and(bit_rshift(reg[30],4),0x0f),16)
local dig_H6 = bit_tosigned(reg[32],8)

local t_fine -- compensate_T() 関数内で値をセットするので、値を参照する時には compensate_T() を先にコールしておくこと

function compensate_T(adc_T)
	local v1 = (adc_T / 16384.0 - dig_T1 / 1024.0) * dig_T2
	local v2 = (adc_T / 131072.0 - dig_T1 / 8192.0) * (adc_T / 131072.0 - dig_T1 / 8192.0) * dig_T3
	t_fine = v1 + v2
	local temperature = t_fine / 5120.0
	return temperature
end

function compensate_P(adc_P)
	local pressure = 0.0
	local v1 = (t_fine / 2.0) - 64000.0
	local v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * dig_P6
	v2 = v2 + ((v1 * dig_P5) * 2.0)
	v2 = (v2 / 4.0) + (dig_P4 * 65536.0)
	v1 = (((dig_P3 * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((dig_P2 * v1) / 2.0)) / 262144
	v1 = ((32768 + v1) * dig_P1) / 32768

	local pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
	if pressure < 0x80000000 then
		pressure = (pressure * 2.0) / v1
	else
		pressure = (pressure / v1) * 2
	end
	v1 = (dig_P9 * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
	v2 = ((pressure / 4.0) * dig_P8) / 8192.0
	pressure = pressure + ((v1 + v2 + dig_P7) / 16.0)  

	return pressure/100
end

function compensate_H(adc_H)
	local var_h = t_fine - 76800.0
	var_h = (adc_H - (dig_H4 * 64.0 + dig_H5/16384.0 * var_h)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * var_h * (1.0 + dig_H3 / 67108864.0 * var_h)))
	var_h = var_h * (1.0 - dig_H1 * var_h / 524288.0)
	if var_h > 100.0 then
		var_h = 100.0
	end
	if var_h < 0.0 then
		var_h = 0.0
	end
	return var_h
end

------------------------------------
-- 現在の測定値取得
------------------------------------
stat,result = raspi_i2c_write(bus,slave_addr,"F7",8)
if not stat then error() end
local raw = hex_to_tbl(result)

local pres_raw = bit_or(bit_lshift(raw[1],12),bit_lshift(raw[2],4),bit_rshift(raw[3],4))
local temp_raw = bit_or(bit_lshift(raw[4],12),bit_lshift(raw[5],4),bit_rshift(raw[6],4))
local hum_raw  = bit_or(bit_lshift(raw[7],8),raw[8])

local temp = compensate_T(temp_raw)
local press =compensate_P(pres_raw)
local humidity =compensate_H(hum_raw)

script_result(g_taskid,"temperature",string.format("%5.1f",temp))
script_result(g_taskid,"pressure",string.format("%7.1f",press))
script_result(g_taskid,"humidity",string.format("%5.1f",humidity))

最初の部分で BME280 補償データレジスタの内容を取得しています。このレジスタ値は工場で設定後は変化しないので、一度取得したデータをabs_agent のグローバル共用データに保存しておきます。次回からこのスクリプトをコールしたときには BME280 補償データレジスタを読みに行かないで、abs_agent に保存したデータを利用するようにしています。また同時に、初回にコールされたときのみ BME280 デバイスの測定パラメータを設定します。

補償データレジスタから補償パラメータを計算した後、現在の計測値を BME280 センサから取得して温度と湿度、気圧データを計算します。計算結果はスクリプトリターンパラメータに設定してスクリプトを終了します。(浮動小数点演算に伴う誤差が伴いますので使用される前にご自身で計算方法と精度を確認して、必要に応じてスクリプトを修正してからご使用ください)

表示アプリケーションからこのスクリプトを定期的にコールして、リターンパラメータで得られたセンサ値を LCD に表示しています。

次に、明るさを測定するスクリプト(RASPI/DEVICE/ENVSENSOR_LIGHT_READ) の内容は下記の様になっています。

 

--[[

●機能概要

環境センサーボード(スイッチサイエンス社製)光センサの値を取得する

●リクエストパラメータ

無し

●リターンパラメータ

---------------------------------------------------------------------------------
キー値          値                                               値の例
---------------------------------------------------------------------------------

light			センサーから取得した値			                  "24"
				明るい時に値が小さく、暗いときに値が大きくなる
				0 .. 255
]]

local slave_addr = "6A"
local bus = 1

------------------------------------
-- 現在の測定値取得
------------------------------------
local stat,result = raspi_i2c_write(bus,slave_addr,"C0",1)
if not stat then error() end

script_result(g_taskid,"light",tonumber("0x" .. result))

このスクリプトは環境センサ基板上に搭載されている、光センサと接続したマイコン(I2Cスレーブ)から1バイトのデータを取得しています。得られた16進数値を10進数に変換した値をリターンパラメータに設定しています。

次に、IP アドレス表示ページで使用される IP アドレス情報は下記のスクリプト(OS/GET_IP)を実行して取得しています。

--[[
******************************************************************************

abs_agent が動作しているコンピュータの IPv4 アドレスを取得する
eth0 以外のインターフェイスを指定する場合にはスクリプトを修正して下さい。

******************************************************************************
]]

local ip_addr = ""
local cmd = [[ip addr show eth0 | grep -o 'inet [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | grep -o [0-9].*]]
local f = assert(io.popen (cmd))
for line in f:lines() do -- 通常は1行しか取得しない筈
	ip_addr = line
end
f:close()

script_result(g_taskid,"IPAddress",ip_addr)

このスクリプトは OS で提供されている “ip” コマンドの出力を “grep” コマンドで加工した後 IP アドレス部分だけを取り出してリターンパラメータに設定します。

abs_agent が動作する Unix 系の OS では幾つかのプログラムやスクリプトをパイプで繋いで、このような仕組みを簡単に実現することができます。パイプ(リダイレクト)の機能を利用することでシステムの構築をプログラム単位でブロックを構成するように組み立てていくことができます。

abs_agent のLua スクリプトは上記の様に、これもパイプ経由で標準出力を取り込むことができますので、複数の OS プログラムやスクリプトで提供されている機能を束ねることで新しい機能を提供するスクリプトを簡単に作成できます。

スクリプト中の “cmd” 変数には、文字列中にシングルコート文字がふくまれているため文字列をリテラル表現で代入しています。上記の例では “[[" と "]]” の間に記述されたOS コマンド文字列が cmd に格納されます。

この項で説明したセンサデータや IP アドレス取得スクリプトを手動で実行してみます。abs_agent のライブラリ関数内部で適切に排他制御が行われますので、LCD 表示アプリケーションを動作させている状態でも問題なく実行することができます。

スクリプト実行には abs_agent インストールキット中に含まれる agent_script プログラムを使用します。コンソールから agent_script -s <スクリプト名> で実行できます。

各スクリプトの実行結果で返されるリターンパラメータに、センサから取得したデータが格納されているのを確認できます。

●アプリケーションスクリプトの説明

ここからは LCD 表示アプリケーション本体のスクリプト(RASPI/ENVSENSOR_DISPLAY_TASK) の内容を説明します。スクリプトの内容は下記の様になっています。

--[[

●機能概要

環境センサーボード(スイッチサイエンス社製)に搭載している BME280 センサと光センサの
測定値を AQM1248A グラフィックLCD に表示する。

●参照するグローバル共有変数

---------------------------------------------------------------------------------
キー値                      値
---------------------------------------------------------------------------------
ENVSENSOR_ACTIVE_PAGE    "1" .. "4"

LCD に表示するページを切り替える。
センサーボードのスイッチ入力に対応したイベントハンドラを設定してこの値を変更すると、
LCD に表示するページを切り替えることができる。

●備考

このスクリプトは無限ループに入って終了しないので、必ず別スレッドで起動してください。

●変更履歴

2017/03/11     初版作成

copyright(c) 2017 All Blue System

]]

-- 2重起動防止用チェック
if not exclusive_check(g_script) then
    log_msg("*ERROR* exclusive_check() failed. script = " .. g_script,file_id)
    return
end
log_msg("start..   TaskID = " .. g_taskid,g_script)

-- 表示ページ切り替えスイッチ(SW1) の GPIO モード設定
-- スイッチ入力時のRASPI_CHANGE_DETECT イベントハンドラで、表示ページ切り替え用のグローバル共有変数を更新すること
local SW_pin = 24
if not raspi_gpio_config(SW_pin,"input","off") then error() end
if not raspi_change_detect(SW_pin,true) then error() end

-- デバイスとタスク内変数初期化

raspi_AQM1248A_try_init()							-- LCD AQM1284A 初期化

local active_page_var = "ENVSENSOR_ACTIVE_PAGE"	-- LCD 表示ページ切り替え用のグローバル共有変数
set_shared_data(active_page_var,"1")				-- 初期表示ページ設定

local timer_interval = 100							-- 表示更新間隔の初期値(10msで割った数を指定)、ページ毎に更新間隔は変わる
local run_indicator = false						-- 画面更新 Indicator
local ip_address = ""								-- 取得した自身の IP アドレスを表示するときに使用

---------------------------------------------------------------------
-- BME280 センサー
---------------------------------------------------------------------
function BME280_page()
	local stat,BME280 = script_exec2("RASPI/DEVICE/BME280_READ","","")
	if not stat then error() end

	graphic_clear()
	graphic_print(22,1,BME280["temperature"] .. "[ ]",2)
	graphic_draw_bitmap(80,32,8,8,list_to_hex(0x02,0x05,0x02,0x3c,0x42,0x42,0x24,0x00),true,true,2) -- "℃" 文字
	graphic_print(22,3,BME280["humidity"] .. "[%]",2)
	graphic_print(6,5,BME280["pressure"] .. "[hP]",2)
	graphic_draw_circle(120,38,6,run_indicator,true)
	raspi_AQM1248A_display()
	timer_interval = 100
end

---------------------------------------------------------------------
-- 光センサー
---------------------------------------------------------------------
function LightSensor_page()
	local stat,ENVSENSOR = script_exec2("RASPI/DEVICE/ENVSENSOR_LIGHT_READ","","")
	if not stat then error() end

	graphic_clear()
	graphic_print(10,2,ENVSENSOR["light"],3)
	graphic_print(70,2,"[Lu]",2)
	local bright = math.ceil(110 * tonumber(ENVSENSOR["light"]) / 255)
	graphic_draw_rect(10,5,110,15,true,false)
	graphic_draw_rect(10,5,bright,15,true,true)
	raspi_AQM1248A_display()
	timer_interval = 30
end

---------------------------------------------------------------------
-- デジタル時計
---------------------------------------------------------------------
function clock_page()
	local now = os.date "*t";
	local stat,wday = day_of_week(now["year"],now["month"],now["day"]);
	if not stat then error() end
	local weekstr;
	if wday == 1 then
		weekstr = "Sun"
	elseif wday == 2 then
		weekstr = "Mon"
	elseif wday == 3 then
		weekstr = "Tue"
	elseif wday == 4 then
		weekstr = "Wed"
	elseif wday == 5 then
		weekstr = "Thu"
	elseif wday == 6 then
		weekstr = "Fri"
	elseif wday == 7 then
		weekstr = "Sat"
	end;

	local data1 = string.format("%2.2d/%2.2d %s",now["month"],now["day"],weekstr)
	local data2 = ""
	if run_indicator then
		data2 = string.format("%2.2d:%2.2d",now["hour"],now["min"])
	else
		data2 = string.format("%2.2d %2.2d",now["hour"],now["min"])
	end

	graphic_clear()
	graphic_print(15,1,data1,2)
	graphic_print(20,5,data2,3)
	raspi_AQM1248A_display()
	timer_interval = 50
end

---------------------------------------------------------------------
-- IPアドレスと時計
---------------------------------------------------------------------
function ip_page()
	if ip_address == "" then
		local stat,info = script_exec2("OS/GET_IP","","")
		if not stat then error() end
		ip_address = info["IPAddress"]
	end

	local now = os.date "*t";
	local data1 = ""
	if run_indicator then
		data1 = string.format("%2.2d:%2.2d",now["hour"],now["min"])
	else
		data1 = string.format("%2.2d %2.2d",now["hour"],now["min"])
	end

	graphic_clear()
	graphic_print(35,1,data1,2)
	graphic_print(0,3,"IP=" .. ip_address,2)
	raspi_AQM1248A_display()
	timer_interval = 50
end

-- 指定したグローバル共有変数の値が変更されるか、もしくは指定されたカウント値 x 10ms 経過するまで内部でウェイト
function global_change_wait(global_name,start_val,max_wait_cntr)
	local stat,new_val
	local cntr = 0
	repeat
		wait_time(10)
		cntr = cntr + 1
		stat,new_val = get_shared_data(global_name)
	until (cntr >= max_wait_cntr) or (start_val ~= new_val)
end

-----------------------------------------------------------------------------------------------------------------
-- メインループ。無限ループを停止させる場合には script_kill() または "agent_task -k <taskid>" コマンドを使用する
-----------------------------------------------------------------------------------------------------------------
local event_stat
while true do
	run_indicator = not run_indicator
	local stat,page = get_shared_data(active_page_var)

	if page == "1" then
		BME280_page()
	elseif page == "2" then
		LightSensor_page()
	elseif page == "3" then
		clock_page()
		ip_address = "" -- ページ切り替え時に ip_page() 内で IP アドレスを再取得させる
	elseif page == "4" then
		ip_page()
	end

	global_change_wait(active_page_var,page,timer_interval)
end

スクリプトの先頭部分で2重起動防止のためのチェックをした後、環境センサ基板の SW1 スイッチの設定を行います。全てのスイッチは SERVER_START スクリプト中で入力モードになっていますので、ここではスイッチを押したときと離したときにイベントを検出する設定を行っています。スイッチ入力が行われる毎に、最初に修正作業を行った RASPI_CHANGE_DETECT イベントハンドラがコールされるようになります。

その後、abs_agent 内部のグラフィックライブラリを初期化しています。abs_agent では LCD グラフィックモジュール表示用のライブラリ関数を提供していて、簡単に文字や図形を描画することができます。グラフィックライブラリの詳細については abs_agent ユーザーマニュアルをご覧ください。

スクリプトの後半は、アプリケーションの表示ページ毎に関数が作成しています。このページ毎の関数は、<timer_interval 変数に格納した値>  x 10ms の間隔で繰り返しコールされます。

BME280_page() 関数では、前述の RASPI/DEVICE/BME280_READ スクリプトをコールして温度、湿度、気圧の測定値を取得しています。その後、グラフィックライブラリを使用して現在の測定値を画面に表示します。

グラフィックライブラリには2種類の ASCII フォントが組み込まれていますので簡単に英数字を表示することができます。日本語や “℃” 等の文字を表示する場合には、この例の様にビットマップパターンを指定することで表示できます。

同様に、LightSensor_page() 関数内では光センサの測定値を取得した後画面に表示します。このとき簡単なバーグラフも描画しています。

clock_page() 関数では OS の内部時計のデータを取得してデジタル時計を表示します。

ip_page() 関数では IP アドレスを表示します。このとき、ページ切り替えを行う毎に IP アドレスを新規に取得させることで、DHCP でアサインされたアドレスが変化したときにも対応できるようにします。

スクリプトの最後の部分がメインループになっていて、それぞれのページを繰り返し表示しています。SW1 を押すと表示するページを示すグローバル共有変数ENVSENSOR_ACTIVE_PAGE の内容が変化しますので、表示するページを切り替えています。

●考察・応用

Raspberry Pi の電源を入れたときに自動的に今回のアプリケーションを自動起動させることもできます。この場合には、Raspbian OS の /etc/rc.local 起動スクリプト中に abs_agent プログラムを起動させるための記述するだけで完了します。詳しい方法はabs_agent ユーザーマニュアルに記載されていますのでご覧ください。

今回のアプリケーションは完全にスタンドアロンで動作します。既存のRaspberry Pi に環境データや時計、IP アドレス表示機能をプラスできます。殺風景なファイルサーバーやWebサーバーを素敵なインテリアとしても活用できます。

また応用例としては、Raspberry Pi のCPU 温度やパフォーマンス情報、MQTT publish メッセージのリアルタイム表示機能も簡単に追加できると思います、ぜひチャレンジしてみてください。

ご意見や質問がありましたら、お気軽にメールをお寄せください(contact@allbluesystem.com) 。

それではまた。