2021年7月31日土曜日

Time server

 半分仕事でやったことなんでここには書いてなかったんですが、これをやっていたとき、並行して、GPSから出力されるppsを使ったタイムサーバーを、NanoPi NEOを使って仕立ててました。

さすが外部のntpサーバーを見ているより高い精度の時刻が得られているようですが、何分バラックセットで運用しているので、ケーブル引っ掛けたり何だりしてしまう関係で、箱に入れることを考えつつ、ただ箱が置かれると、家族的には『なにこれ?』となることを請け合いなので、何か表示でもしておこう、というところに取りかかることにしました。

LCDつなげる

実際、ntpサーバーって地味ですよね…。ただ動いていることを示しておきたいだけなので、今まで、手元にはありつつ、Arduinoで使おうかなーと思いつつ調達したものの、眠っていたキャラクタLCDを使ってみることにしました。たしか、こちらだと思いますが、バックライト用のピン位置が標準とは違うものです。

4ビットモードで使えるだろうとはいえ、Linuxサイドからなるべく簡単にコトませたいので、アマゾン頼みI2C I/Fを調達し手搭載(このLCDでは、バックライト端子のみ別配線する工夫が必要ですが…)、NanoPiへの配線も4線で。Osoyoさんのサンプルコードを使って、表示確認まで済ませました。

何を表示するのか?

キャラクタLCD、16文字2桁しかないし、つけっぱなしで寿命どのくらいなんかなーと言うことが若干気になりつつもまぁ5年くらいは動くのかな…。

で、表示内容は、こいつがなんなのか(つまり"Time Server")、GPSから得られるその日付時刻(UTCですが…)、測位モードにエアの状態、衛星は見えてるかいくつ? くらい、さすればこのntpは今GPS使えてるか自走モードかくらいは判別できるので、その辺繰り返し表示しておけばよいかなー。

どやって表示するか?

ntpサーバーはgpsd使っているし、表示テストに使ったサンプルコードはpythonだしそれなら後で表示内容簡単に変えられるし、そこにgps情報を付け加えればえっかなと、ざっくり。"python gpsd"でググるとgps3というpythonパッケージが目に止まって動かしてみる。何々?十分な情報とれそうだ、でもそんなに色々情報使いはしないけど、と思って使ってみると、測位に使っている衛星個数情報を取り出せない。ま、測位モードだけ判れば良いかとおもい、その表示を行わせてみると、時間更新がうまくいかないし、どうもgpsdから与えられる情報にゴミが含まれることがあるのか、クラッシュする(pythonプログラムが落ちるわけではないが止まってしまう)。使っているGPSその物に依存する可能性はあるけど、これじゃちょっと辛い…。

改めて検索しこれを試すと、シンプルだし時刻データもちゃんと更新される。osoyoのコードそれを付け加えたのが次のコード。gps3が問題なく動くなら、satellitesも同じようにリストになってると思うので、それ調べて測位に使われているならカウントしてやれば良いだろうと思う。


#
# Project Tutorial Url:http://osoyoo.com/?p=1031
#
# GPSd related codes: class GpsPoller() etc.
# Written by Dan Mandle http://dan.mandle.me September 2012
# License: GPL 2.0
#  
import smbus
import time
import threading
from gps import *
gpsd = None
class GpsPoller(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        global gpsd
        gpsd = gps(mode=WATCH_ENABLE)
        self.current_value = None
        self.running = True

    def run(self):
        global gpsd
        while gpsp.running:
            gpsd.next()
# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address, if any error, change this address to 0x3f
LCD_WIDTH = 16   # Maximum characters per line
# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off
ENABLE = 0b00000100 # Enable bit
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
#Open I2C interface
bus = smbus.SMBus(0)  # NanoPi NEO / Rev 1 Pi uses 0
#bus = smbus.SMBus(1) # Rev 2 Pi uses 1

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display
  time.sleep(E_DELAY)

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = the data
  # mode = 1 for data
  #        0 for command

  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
  bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT
  # High bits
  bus.write_byte(I2C_ADDR, bits_high)
  lcd_toggle_enable(bits_high)

  # Low bits
  bus.write_byte(I2C_ADDR, bits_low)
  lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
  # Toggle enable
  time.sleep(E_DELAY)
  bus.write_byte(I2C_ADDR, (bits | ENABLE))
  time.sleep(E_PULSE)
  bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
  time.sleep(E_DELAY)
def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

def main():
  # Main program block

  # Initialise display
  lcd_init()

  gpsp.start()

  while True:
    gps_satellites = gpsd.satellites
    num_sats_available = 0
    for item in gps_satellites:
        satdata = str(item)
        num_sats_available = num_sats_available + satdata.count("Used: y")
    lcd_string("GPS time server ",LCD_LINE_1)
    gps_mode = gpsd.fix.mode
    if gps_mode == 1:
        mode_string = "SATs not fixed"
    elif gps_mode == 2:
        mode_string = "2D mode, "+str(num_sats_available)+"SATs"
    elif gps_mode == 3:
        mode_string = "3D mode, "+str(num_sats_available)+"SATs"
    else:
        mode_stroing = "Unknown"
    lcd_string(mode_string, LCD_LINE_2)

    time.sleep(2)
  
    # next page display -- time
    gps_time = gpsd.utc
    dt_today = gps_time[:10]
    dt_time = gps_time[11:20]+"(UTC)"

    lcd_string(dt_today,LCD_LINE_1)
    lcd_string(dt_time,LCD_LINE_2)
 
    # next page display -- time
    gps_time = gpsd.utc
    dt_today = gps_time[:10]
    dt_time = gps_time[11:20]+"(UTC)"

    lcd_string(dt_today,LCD_LINE_1)
    lcd_string(dt_time,LCD_LINE_2)

    time.sleep(3)


if __name__ == '__main__':
  gpsp = GpsPoller()
  try:
    main()
  except (KeyboardInterrupt, SystemExit):
      print ("\nKilling Thread...")
      gpsp.running = False
      gpsp.join()
  finally:
     lcd_byte(0x01, LCD_CMD)


以上、テストラン中。後は箱作りですが、それはまた今度。最悪、LCDバックライト寿命については、ジャンパでon/offできるので、トグルスイッチでもつけるかな…。