Raspberry piでデジタルサイネージを作ってみた

 2020.09.25  株式会社テリロジー 技術統括部

こんにちは('ω')ノ

2年目社員のTachanです。

先日の記事ですが、たくさんの方に見ていただきました。
ありがとうございます。

今回は、人生初の開発物、「デジタルサイネージ」について書いていきたいと思います!! ( ・´ー・`)ドヤァ

Raspberry piでデジタルサイネージを作ってみた 1

今回の記事の目次です。

デジタルサイネージの概要、用途

デジタルサイネージとは何でしょうか。
普段よく見るのに、用語は入社してから知りました…。

デジタルサイネージとは、紙ではなく電子による広告のことで、新しい情報伝達媒体として利用されています。

駅やショッピングモールなどで見かることが多いです。

Raspberry piでデジタルサイネージを作ってみた 4    Raspberry piでデジタルサイネージを作ってみた 2

これを会社用に開発して、来社いただいたお客様に見てもらえるようなデジタルサイネージを作ろう!ということで開発計画が始まりました。

ですが、例のウイルスの影響により、お客様向けではなく、社内の情報共有に利用することになりました。

アラーΣ(゚Д゚)

(一番の目的は、私のpythonのスキル習得なので問題なしです!)

構成図、運用方法

今回の開発で使用したものは、

  • Raspberry pi
  • モニター
  • コンセントタイマー

です。

大まかな構成はこのようにしました。

Raspberry piでデジタルサイネージを作ってみた 3

処理の流れとしては

  1. コンセントタイマーで指定時間に通電し、Raspberry piを起動
  2. cronでスクリプトを実行
  3. cronで指定時間にシャットダウン実行
  4. コンセントタイマーで指定時間に通電を遮断

となります。

詳しい処理は3で書きます。

④を行う理由ですが、ラズパイは電気を流すことで起動します。
終了後に遮断しておかないと、起動する手段が無くなります。

コードの処理内容

コード作成にあたり、次のモジュールやソフトウェアを使いました。

①pdfminer
pythonのモジュールで、PDFファイルからテキストを抽出することができます。
また、総ページ数のカウントが可能です。
今回は、ページのカウント機能を使いました。

②evince
複数のドキュメント形式(PDF、Postscriptなど)に対応したオープンソースのドキュメントビュアーです。
プレゼンテーションモードで表示することも可能です。
今回はevinceを実行してコンテンツを画面表示させました。

③xautomation
LinuxのX Windows Systemを制御し、キーボード・マウス自動入力などができるオープンソースのアプリケーションソフトウェアです。
evinceだけだと表示するだけでスライドの移動ができないため、この機能でマウスをぽちぽちさせることで次のスライドに進めます。

以下が実際のコードです。

  1. #!/usr/bin/python
  2.  
  3. import glob
  4. from pdfminer.pdfparser import PDFParser
  5. from pdfminer.pdfdocument import PDFDocument
  6. from pdfminer.pdfpage import PDFPage
  7. from subprocess import check_output
  8. import subprocess
  9. import os
  10. import sys
  11. import time
  12.  
  13.  
  14. class croncheck(object):
  15. def __init__(self, cmd):
  16. self.cmd = cmd
  17.  
  18. def cron_check(self):
  19. try:
  20. res = subprocess.Popen(self.cmd, shell=True, stdout=subprocess.PIPE)
  21. stdout,strerr = res.communicate()
  22. res_decode = stdout.decode('utf-8')
  23. res_str = int(res_decode)
  24.  
  25. if res_str > 2: #another cron exists
  26. return True
  27. else: #any cron does not exist
  28. return False
  29.  
  30. except Exception as e:
  31. with open('フルパスのファイル名', mode='a') as f:
  32. f.write(str(e) + '\n')
  33. return False
  34.  
  35.  
  36. class files_and_pages(object):
  37. def __init__(self):
  38. self.FileList = []
  39. self.Pagenumber = []
  40.  
  41. def get_files_and_pagenumber(self):
  42. files = glob.glob(os.path.dirname(os.path.abspath(__file__)) + "/PDF/*.pdf") #get all pdf name
  43. for file in files:
  44. fp = open(file, 'rb')
  45. parser = PDFParser(fp)
  46. document = PDFDocument(parser)
  47. num_pages = 0
  48. for page in PDFPage.create_pages(document): #count page number
  49. num_pages += 1
  50.  
  51. self.FileList.append(file) #append to Filelist
  52. self.Pagenumber.append(num_pages) #append to page number
  53. print(self.FileList, self.Pagenumber)
  54.  
  55.  
  56.  
  57.  
  58. class evince_and_pid(object):
  59.  
  60. def __init__(self, t, w):
  61. self.t = t
  62. self.w = w
  63.  
  64.  
  65. def do_evince(self):
  66. print(len(File_and_Pages.FileList))
  67. for i in range(len(File_and_Pages.FileList)):
  68. Pages = File_and_Pages.Pagenumber[i]
  69. PID1 = self.get_pid('evince', '') #get current evince pid
  70. print(PID1)
  71.  
  72. args = ['evince', '-s', File_and_Pages.FileList[i]]
  73. args_mousemove = ['xte', 'mousemove 900, 510, 900, 510']
  74. args_mouseclick = ['xte', 'mouseclick 1']
  75.  
  76. print(File_and_Pages.FileList[i], Pages)
  77. res = subprocess.Popen(args) #execute evince
  78. time.sleep(self.t)
  79. PID2 = self.get_pid('evince', PID1) #get new evince pid
  80. print(PID2)
  81. print(PID1, PID2)
  82.  
  83. if PID1 != None:
  84. self.kill_pid(int(PID1), 9)
  85.  
  86. PID1 = PID2
  87.  
  88. for j in range(Pages): #move on to the next slide
  89. print(File_and_Pages.FileList[i] + str(j))
  90. res_mousemove = subprocess.call(args_mousemove)
  91. time.sleep(self.w)
  92. res_mouseclick = subprocess.call(args_mouseclick)
  93. sys.exit()
  94.  
  95.  
  96.  
  97. def get_pid(self, name, last_pid):
  98. try:
  99. pid_byte = check_output(["pidof",name]) #return byte type
  100. pid_str = pid_byte.decode('utf-8') #change to standard character code(can't compare byte with string)
  101. pid_array = pid_str.split(' ') #separate with space
  102. if len(pid_array) == 0:
  103. return
  104. else:
  105. for current_pid in pid_array:
  106. current_pid = current_pid.replace('\n', '')
  107. if current_pid != last_pid: #lastpid = PID1
  108. return current_pid
  109. except:
  110. return
  111.  
  112.  
  113. return check_output(["pidof",name])
  114.  
  115.  
  116. def kill_pid(self, pid, number):
  117. if pid != None:
  118. os.kill(pid, number)
  119.  
  120.  
  121. def signage_loop():
  122. if Cron_Check.cron_check(): #another cron exists
  123. sys.exit()
  124. else: #if cron does not exit, execute follow
  125. File_and_Pages.get_files_and_pagenumber()
  126. Evince_and_pid.do_evince()
  127.  
  128.  
  129. if __name__ == "__main__":
  130. CRON = 'ps -aux | grep 実行ファイル名.py | grep -v -w vi | grep -v -w nano | grep -v -w grep | wc -l'
  131. TIME = 3
  132. WAIT = 15
  133.  
  134. File_and_Pages =files_and_pages()
  135. Evince_and_pid = evince_and_pid(TIME, WAIT)
  136.  
  137. Cron_Check = croncheck(CRON)
  138.  
  139.  
  140. signage_loop()

難しかった処理は、PIDの取得と多重起動チェックの処理です。

PIDでは、コマンドを実行するとByte型で返ってくるので、その値を変数に入れたい場合、UTF-8に変換する必要がありました。

見た目が同じでも文字コードが違うことに全く気づけず、時間がかかってしまいました。

多重起動チェックでは、if文の条件式にうまく入らず、1分おきにcronを実行していたので実行ファイルが複数動いてしまいました。

原因は、def signage_loop内の書き方ミス、def cron_check内の条件式のミスでした。

諸先輩方のご協力でなんとか修正することができました…(__)

さいごに

今回の開発を通して、pythonのスキル習得、構成図の書き方、段取りを学びました。

構成図や段取りは他の業務にも生かしていきたいと思います。

デジタルサイネージの今後としては、この機能を拡張して、社内の情報共有を活発にさせたいと思います!

最後までお読みいただきありがとうございました。


RECENT POST「技術コラム」の最新記事


技術コラム

Sumo Logic ダッシュボードやフォルダ共有について

技術コラム

Sumo Logicのアラート作成方法について

技術コラム

Sumo Logic Collecterの設定移行の方法  

技術コラム

サイバーセキュリティとレイヤーディフェンスについて

Raspberry piでデジタルサイネージを作ってみた