しまねソフト研究開発センターでは、IoT分野における先端技術支援や研究活動の一環として、mruby/cを使ったIoTデバイスの開発・製作を行っております。

このページでは、機械設備や装置の稼働を計測するニーズに対して、簡易にデータを計測・収集するためのAC電流ロガーの開発を行いましたので、その取組事例をご紹介いたします。

製品外観 内部正面

目次

1.概要
2.内容
  2-1.ドキュメント
  2-2.サンプルコード
  2-3.注意事項
  2-4.執筆者紹介
3.お問い合わせ先

概要

目的

  • 主に製造業の機械設備や装置において、稼働状況を「見える化」すれば作業を平準化するフローを導入する事で「生産性の向上」が図れます。
  • しかしながら、機械設備や装置にインターネット通信機能が搭載していない、またはオプション機能で実現できるものの導入コストが高いといった課題があります。
  • そこで、上記の課題を解決するため、より簡単かつ低コストで機械設備や装置の稼働を計測可能なAC電流ロガーを開発しました。

AC電流ロガーについて

  • 今回、カレントトランスでセンシングしたAC電流を、mruby/cを標準搭載するマイコンボード「RBoard」のADコンバータを使って測定を行い、一定時間ごとにWi-Fi経由でサーバへ送信するものです。
  • これにより、既に導入された機械設備や装置でも取り付けが容易となるクランプ型電流センサーを採用しており、測定値はサーバ上でリアルタイムに確認できるとともに、稼働状況を判定することができます。

内容

AC電流ロガーの開発にあたり、ハードウェアおよびソフトウェア、動作実績、基板回路図に関するドキュメントを公開いたします。
併せて、サンプルコードを公開しておりますので、予め注意事項をご確認の上、参照ください。

ドキュメント

このAC電流ロガー開発に関するドキュメントは、以下のPDFファイルでご覧いただけます。
併せて、AC電流ロガー開発で用いたパーツリストをご覧いただけます。

pdfファイル「ドキュメント(AC電流ロガー開発について)」をダウンロードする(PDF:496kB)

pdfファイル「パーツリスト(AC電流ロガーについて)」をダウンロードする(PDF:80kB)

サンプルコード

main.rb

# coding: utf-8
#
# カレントトランスを使った電流測定サンプル
#
#  Copyright (C) 2022 Shimane IT Open-Innovation Center.
#
#  This file is distributed under BSD 3-Clause License.
#

POST_URL = "http://example.jp/cgi-bin/datalogger.rb?ctrl=ct&action=post"
CT_RATIO = 3000 / 100   # カレントトランスの変流比 / 接続する抵抗値 (オーム)
SLEEP_TIME = 10         # 一回測定ごとにスリープする時間 (秒)
GPIO_WIFI_RESET = 2     # WiFi reset pin に繋いだ GPIO Pin 番号

##
# AC波形の測定
#
# (note)
#  100ms間のAC波形を取得する
#  この回路ではマイナス電圧が測定できず全てゼロになるため、
#  プラス側とマイナス側が同じ波形で電流が流れていると仮定して計算している。
#
def ct_meas( adc )
  sum = 0.0
  n = 0
  t1 = VM.tick() + 100

  while VM.tick() < t1
    v = adc.read
    sum += v*v
    n += 1
  end

  return Math.sqrt( 2 * sum / n ) * CT_RATIO
end


#
# send data to server
#
def send_data( data )
  if !$wifi.wait_for_connect()
    wifi_hard_reset()
    return nil
  end

  res = $wifi.http_post_json( POST_URL, data )
  $wifi.reboot()  if !res

  return res
end


#
# WiFiモジュールのハードウェアリセット
#
def wifi_hard_reset()
  $wifi_reset.write( 0 )
  sleep 1
  $wifi_reset.write( 1 )
end


#
# main
#
sleep 2
puts "AC current maasurement system"
$adc = ADC.new(20)

$wifi_reset = GPIO.new( GPIO_WIFI_RESET )
$wifi_reset.setmode( 0 )
$wifi_reset.write( 1 )
$wifi = AMW037.new( UART.new( 115200 ))

#
# WiFi module setup
#
#$wifi.setup_module("SSID", "PASSWORD"); $wifi.reboot; sleep 1000

while !(mac_address = $wifi.mac_address())
  puts "WiFi mac address error."
  wifi_hard_reset()
end
puts "WiFi mac address is #{mac_address}"

send_count = 1
while true
  i_ac = ct_meas( $adc )
  printf "I = %.2f A(rms)\n", i_ac
  data = {:type=>"AC current", :mac_address=>mac_address,
          :i_ac=>i_ac, :count=>send_count }
  if send_data( data )
    send_count += 1
    puts "Data send OK."
  else
    puts "ERROR: Data send failed."
  end

  sleep SLEEP_TIME
end

amw037.rb

# coding: utf-8
#
# Silicon Labs WiFi module AMW037 control class.
#
#  Copyright (C) 2015-2022 Shimane IT Open-Innovation Center.
#
#  This file is distributed under BSD 3-Clause License.
#


##
# debug print
#
def dp( s )
  puts s
end

class Hash
  def to_json_tiny
    ret = "{"
    flag_comma = false
    self.each {|k,v|
      ret << ", "  if flag_comma
      ret << %!"#{k}":#{v.inspect}!
      flag_comma = true
    }
    ret << "}"
  end
end


##
# AMW037 Class
#
class AMW037

  attr_reader :is_read_timeout

  ##
  # constructor
  #
  def initialize( node = nil )
    @rfm = node || UART.new( 1 )
    clear_buffer()
  end


  ##
  # clear tx/rx buffer
  #
  def clear_buffer()
    @rfm.clear_tx_buffer()
    @rfm.clear_rx_buffer()
    @is_read_timeout = false
  end


  ##
  # send any command
  #
  def command( cmd )
    dp ">>>SEND \"#{cmd}\""
    @rfm.write("#{cmd}\r\n")
  end


  ##
  # get string with timeout
  #
  def get_string( timeout = 1000 )
    @is_read_timeout = false
    cnt = 0
    timeout /= 10

    while cnt < timeout
      txt = @rfm.gets()
      return txt  if txt
      cnt += 1
      sleep_ms 10
    end

    @is_read_timeout = true
    return nil
  end


  ##
  # chat
  #
  #@param [String]      send    message.
  #@param [Integer]     timeout timeout (ms)
  #@return [String]             result message
  #@return [nil]                seaquence error.
  #@return [false]              status code error.
  #
  def chat( send, timeout = 1000 )
    if send
      dp ">>>C:SEND \"#{send}\""
      @rfm.write("#{send}\r\n")
    end

    # (see)
    # https://docs.zentri.com/zentrios/wl/latest/serial-interface#response-format
    @res_code = get_string( timeout )
    dp "<<<C:RES  #{@res_code.inspect}"
    return nil  if !@res_code || @res_code[0] != "R"
    @res_code.chomp!

    len = @res_code[2,5].to_i
    @res_text = nil
    cnt = 0
    if len > 0
      while (cnt += 1) < 100
        @res_text = @rfm.read( len )
        break  if @res_text
        sleep_ms 10
      end
    end
    @is_read_timeout = (cnt == 100)

    if @res_text
      @res_text.chomp!
      dp "<<<C:TEXT #{@res_text.inspect}"
    else
      @res_text = ""
    end

    return false  if !@res_code.start_with?("R0")
    return @res_text
  end


  ##
  # reboot
  #
  def reboot()
    command("\r\nreboot")
    sleep 5
    clear_buffer()
  end


  ##
  # get MAC address
  #
  #@return [String]     mac address
  #@return [nil]        retry error.
  #
  def mac_address()
    mac = nil
    retry_count = 10

    while !mac
      clear_buffer()
      command("get wlan.mac")
      while mac = get_string()
        dp "<<<RECV #{mac.inspect}"
        break  if mac.size == 19
      end

      return nil  if (retry_count -= 1) == 0
      sleep 1
    end

    return mac.chomp
  end


  ##
  # setup module
  #
  def setup_module( ssid = nil, passkey = nil )
    # factory reset.
    mac = mac_address()
    @rfm.write("factory_reset #{mac}\r\n")
    puts ">>> SEND: factory_reset #{mac}"
    sleep_ms 5000
    puts "<<< RECV: " + @rfm.read_nonblock(1000).to_s

    # to machine friendly mode.
    # and set wlan parameter
    cmds = <<-EOL.split("\n")
set setup.gpio.control_gpio -1
set system.print_level 0
set system.cmd.header_enabled 1
set system.cmd.prompt_enabled 0
set system.cmd.echo off
set wlan.hide_passkey 1
set wlan.auto_join.enabled 1
EOL
    cmds << "set wlan.ssid #{ssid}\n"  if ssid
    cmds << "set wlan.passkey #{passkey}\n" if passkey
    cmds << "save\n"

    cmds.each {|cmd|
      @rfm.write( cmd + "\r\n" )
      puts ">>> SEND: " + cmd
      sleep_ms 500
      puts "<<< RECV: " + @rfm.read_nonblock(1000).to_s
    }
  end


  ##
  # web setup mode
  #
  def setup_web()
    return chat("setup web") == "In progress"
  end


  ##
  # start WiFi connection
  #
  def start_connect()
    return  if chat("network_up") == "In progress"
    sleep_ms 100
    clear_buffer()
  end


  ##
  # connected now?
  #
  def is_connected()
    return chat("get wlan.network.status") == "2"
  end


  ##
  # wait for connect
  #
  #@return [Boolean]    connect / disconnect
  #
  def wait_for_connect()
    return true  if is_connected()

    retry_cnt = 0
    while retry_cnt < 3
      start_connect()
      cnt = 0
      while (cnt += 1) < 60
        return true  if is_connected()
        sleep 1
      end

      retry_cnt += 1
    end
    return false
  end


  ##
  # HTTPサーバーへ、JSONデータをPOSTする
  #
  #@param [String] url  URL (e.g. http://example.com/cgi-bin/sample.cgi)
  #@param [Hash]   data data hash.
  #@return [Array<Integer,String>]      returned status and contents.
  #@return [Nil]                        error.
  #
  def http_post_json( url, data )
    data_json = data.to_json_tiny()
    handle = chat("http_post -o #{url} -l #{data_json.size} application/json", 10000)
    return nil  if !handle

    command("stream_write #{handle} #{data_json.size}")
    @rfm.write(data_json)

    ret = nil
    while !ret
      break if !chat(nil)

      status_code = chat("http_read_status #{handle}", 30000)
      break if !status_code
      status_code = status_code.to_i

      contents = read_stream( handle, 1000 )
      break if !contents

      ret = [status_code, contents]
    end

    chat("stream_close #{handle}")
    return ret
  end


  ##
  # Read data from stream
  #
  #@param [String,Integer] handle       handle
  #@param [Integer] max_len             maximum length of return size.
  #
  def read_stream( handle, max_len = nil )
    ret = ""
    cnt = 0

    while true
      dp ""
      res = chat("stream_poll #{handle} -r")
      break  if !res
      status,size = res.split(",")      # get status and data size
      size = size.to_i

      # break if no data status continue 5 seconds.
      # because ZentriOS cannot detect closed HTTP stream
      if status == "0"
        break if (cnt += 1) > 5
        sleep 1
        next
      end
      cnt = 0

      # read chunk data. max 1000 bytes.
      # https://docs.zentri.com/zentrios/wl/1.5/cmd/commands#stream-read
      while size > 0
        size2 = size
        size2 = 1000  if size2 > 1000
        size -= size2

        command("stream_read #{handle} #{size2}")
        res = get_string()      # "Rxxxxxx"
        return nil  if !res || !res.start_with?("R0")

        # read size2 bytes.
        cnt2 = 0
        while size2 > 0
          if data = @rfm.read_nonblock(size2)
            dp ">>> Readed size #{data.size}"
            ret << data  if !max_len || ret.size < max_len
            size2 -= data.size
            cnt2 = 0
          else
            return nil  if (cnt2 += 1) > 100
            sleep_ms 10
          end
        end
        get_string()            # skip CRLF
      end

      break  if status == "2"   # break if connection has closed.
    end

    if max_len && ret.size > max_len
      return ret[0, max_len]
    end
    return ret
  end

end
 

注意事項

  • 本事例の掲載情報の閲覧及び利用により、利用者自身、もしくは第三者が被った損害に対して、直接的、間接的を問わず、しまねソフト研究開発センターは責任を負いかねます。
  • 本事例の内容を実践する中で用意された機器やパーツについてのご質問は、それぞれの機器やパーツの提供元にお問い合わせをお願いします。なお、機器やパーツの仕様は、本事例の公開当時のものです。

執筆者紹介

しまねソフト研究開発センター
東 裕人(Higashi Hirohito)/ 専門研究員

しまねソフト研究開発センターの専門研究員として、IoT分野で活用が期待できる小型デバイス向け開発言語「mruby/c」の研究開発、企業や大学・高専などとの共同研究、県内ITエンジニアの技術相談対応などの活動を行っています。詳しいプロフィールはこちら


*このページで公開されている情報は2022年3月31日時点のものです。

お問い合わせ先

しまねソフト研究開発センター(担当:渡部)
Phone:0852-61-2225
Email:itoc@s-itoc.jp