Quantcast
Channel: Under Power 研究所
Viewing all 201 articles
Browse latest View live

Socket Debuggerを使ってみる! ポート:9!簡単なWeb Server

$
0
0
SocketDebugger
こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html
をまず見てね!

前回のスクリプトがイマイチだったので、method GETのスクリプトをまず改造してみる。
今回はファイルパスを検索するのに連想配列を使う。ファイルの追加は容易になると思う。

---------------------------------------------
-- method get
---------------------------------------------
function methodGet( url, ver )
  local file = {}
  local methodTbl = 
  {
    { url = "/",           path = "D:\\temp\\index.html" },
    { url = "/index.html", path = "D:\\temp\\index.html" },
    { url = "/index.htm",  path = "D:\\temp\\index.html" },
  }

  for i = 1, #methodTbl do
    local urlStr = CharFromTbl( url )
    -- local verStr = CharFromTbl( ver )

    if strCmp( urlStr, methodTbl[i].url ) == 0 then
      file = FileRead( methodTbl[i].path )
      return file
    end
  end

  return nil

end

---------------------------------------------
-- compare string A and string B.
---------------------------------------------
function strCmp( stringA, stringB )
  local strLen = string.len( stringA )
  if strLen ~= string.len( stringB ) then 
    return -1 
  end

  for i = 1, strLen do
    if string.byte( stringA, i ) ~= string.byte( stringB, i ) then
      return -1
    end
  end

  return 0
end


※ もう少し上手く書ける気がする。修行が足らん、、、
※ お役立ちリンク
https://qiita.com/peg/items/c472f7a7d9fcca1b5cb7
https://symfoware.blog.fc2.com/blog-entry-455.html
※ 文字列で処理した方がお得なのか?、テーブルで処理した方がお得なのか?、、、悩みますな。









Socket Debuggerを使ってみる! ポート:10!簡単なWeb Server

$
0
0
SocketDebugger
こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html
をまず見てね!

と言う訳で先人の知恵を借りて、文字列で処理をする事に。差分のみ

---------------------------------------------
-- http request
---------------------------------------------
function httpRequest( request )
  local len = #request
  if len < 4 then return 0 end

  local result = 0
  if request[len - 3] == 0x0D and
     request[len - 2] == 0x0A and 
     request[len - 1] == 0x0D and 
     request[len - 0] == 0x0A then
    Logput( 1, 'end of the request message.' )


    local reqStr = CharFromTbl( request )
    local lineTbl = strSplit( reqStr, "\r\n" )
    local line1Tbl = strSplit( lineTbl[1], " " )

    local methodStr = line1Tbl[1]
    local url = line1Tbl[2]
    local httpVersion = line1Tbl[3]

    if strCmp( methodStr, 'GET' ) == 0 then
      local data = methodGet( url, httpVersion )
      if data ~= nil then 
        index200( data )
        result = 200
      else
        index404()
        result = 404
      end
    elseif strCmp( methodStr, 'PUT' ) == 0 then
      index405()
      result = 405
    elseif strCmp( methodStr, 'POST' ) == 0 then
      index405()
      result = 405
    elseif strCmp( methodStr, 'DELETE' ) == 0 then
      index405()
      result = 405
    else
      index400()
      result = 400
    end

  end

  return result
end


---------------------------------------------
-- method get
---------------------------------------------
function methodGet( urlStr, verStr )
  local file = {}
  local methodTbl = 
  {
    { url = "/",           path = "D:\\temp\\index.html" },
    { url = "/index.html", path = "D:\\temp\\index.html" },
    { url = "/index.htm",  path = "D:\\temp\\index.html" },
  }


  for i = 1, #methodTbl do
    if strCmp( urlStr, methodTbl[i].url ) == 0 then
      file = FileRead( methodTbl[i].path )
      return file
    end
  end


  return nil
end


---------------------------------------------
-- divide string by ts and enter table.
--  https://qiita.com/peg/items/c472f7a7d9fcca1b5cb7
---------------------------------------------
function strSplit(str, ts)
  -- 引数がないときは空tableを返す
  if ts == nil then return {} end

  local t = {} ; 
  i=1
  for s in string.gmatch(str, "([^"..ts.."]+)") do
    t[i] = s
    i = i + 1
  end

  return t
end


※ お役立ちリンク
https://qiita.com/peg/items/c472f7a7d9fcca1b5cb7
https://symfoware.blog.fc2.com/blog-entry-455.html








Socket Debuggerを使ってみる! ポート:11!Mime Typeの簡単な実装

$
0
0
SocketDebugger
こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html
をまず見てね!

webServer_005.png前回まででは、コンテンツはhtml文として決め打ちでしたが、今回はmime typeを指定していろいろなファイルの転送を可能とします。


webServer_003.pngこれにより、ブラウザの左上にfaviconが表示されるようになったり、


webServer_006.pngCSVファイルのダウンロードなんかもできますね。


変更部分のみ
---------------------------------------------
-- http request
---------------------------------------------
function httpRequest( request )
  local len = #request
  if len < 4 then return 0 end

  local result = 0
  if request[len - 3] == 0x0D and
     request[len - 2] == 0x0A and 
     request[len - 1] == 0x0D and 
     request[len - 0] == 0x0A then
    Logput( 1, 'end of the request message.' )


    local reqStr = CharFromTbl( request )
    local lineTbl = strSplit( reqStr, "\r\n" )
    local line1Tbl = strSplit( lineTbl[1], " " )

    local methodStr = line1Tbl[1]
    local urlStr = line1Tbl[2]
    local httpVersionStr = line1Tbl[3]

    if strCmp( methodStr, 'GET' ) == 0 then
      local fileData = {}
      local mimeType = ""
      fileData, mimeType = methodGet( urlStr, httpVersionStr )
      if fileData ~= nil and mimeType ~= "" then 
        index200( fileData, mimeType )
        result = 200
      else
        index404()
        result = 404
      end
    elseif strCmp( methodStr, 'PUT' ) == 0 then
      index405()
      result = 405
    elseif strCmp( methodStr, 'POST' ) == 0 then
      index405()
      result = 405
    elseif strCmp( methodStr, 'DELETE' ) == 0 then
      index405()
      result = 405
    else
      index400()
      result = 400
    end

  end

  return result
end


---------------------------------------------
-- method get
---------------------------------------------
function methodGet( urlStr, verStr )
  local file = {}
  local methodTbl = 
  {
    { url = "/",           path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/index.html", path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/index.htm",  path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/favicon.ico",  path = "D:\\temp\\favicon.ico", mime = "image/png" },
    { url = "/text.txt",  path = "D:\\temp\\text.txt", mime = "text/plain" },
    { url = "/photo1.jpg",  path = "D:\\temp\\photo1.jpg", mime = "image/jpeg" },
    { url = "/image1.png",  path = "D:\\temp\\image1.png", mime = "image/png" },
    { url = "/image2.bmp",  path = "D:\\temp\\image2.bmp", mime = "image/bmp" },
    { url = "/test.csv",  path = "D:\\temp\\test.csv", mime = "text/csv" },
--    { url = "/test.bin",  path = "D:\\temp\\test.bin", mime = "application/octet-stream" },
  }


  for i = 1, #methodTbl do
    if strCmp( urlStr, methodTbl[i].url ) == 0 then
      file = FileRead( methodTbl[i].path )
      return file, methodTbl[i].mime
    end
  end


  return nil,""
end


---------------------------------------------
-- http request status code = 200
---------------------------------------------
function index200( data, mimeType )
  local contentLength = #data
  local contentHeader = 'HTTP/1.1 200 OK\r\n'
    .. 'Server: SocketeEbugger Web Server/ ver.0.1\r\n'
    .. 'Content-Type: ' .. mimeType .. ' charset=UTF-8\r\n'
    .. 'Content-Length: ' .. contentLength
    .. '\r\n'
    .. 'Connection: close\r\n\r\n'

  SendData( contentHeader )
  SendData( data )
end



※ お役立ちリンク
https://asahi-net.jp/support/guide/homepage/0017.html








Socket Debuggerを使ってみる! ポート:12!POSTの実装に向けて

$
0
0
SocketDebugger
こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html
をまず見てね!

webServer_007.png内部処理を文字列で行うのか?テーブルで行うのか?どっちが得なんだよ!って事でテーブルで処理する事とします。
なんかあれですね、ファイル書き込みにappendモードが無いので受信データを一旦全部テーブルに保存している訳です。ファイル受信試験で数十kbyteのファイルはイイですが、ちょっと大きな、と言っても4Mbyte程度のjpgですがね、メモリが足りなくなってスクリプトの実行エラーが起きますね。
でも同じJPGファイルの送信時には発生しないのは謎ですね。


webServer_008.pngあとあれですね、全体的に転送が重いので、細かく多量の処理をするのではなく、こうまとまった受信データをいっきに処理したい場合は、内部プログラム受信バッファサイズを大きくとってしまいましょう!と言う話でやんす。



ソース全文
---------------------------------------------
-- 接続完了通知
---------------------------------------------
function OnConnected()
  Logput(1,'OnConnected')

  recvHttpTable = {}  -- 受信データの一時格納用
  recievedContentTable = {}  -- HTTP文の内容格納用
  recievedUrlStr = ""  -- HTTP文のURL
  contentLength = 0  -- HTTP文の内容の取り込むべきサイズ
  operationCode = 0  -- 処理コード
 
 return 0
end
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
    Logput(1,'OnSendPush')
    a = GetEditorData()
    SendData(a)
    return 0
end
---------------------------------------------
-- タイマー通知
---------------------------------------------
function OnTimer(id)
    Logput(1,'OnTimer')
    return 0
end
---------------------------------------------
-- 受信通知
-- 0x0D is CR,0x0A is LF
---------------------------------------------
function OnReceive(recv)
  Logput(1,'OnReceive')

  if operationCode == 0 then  -- 初期状態?
    recvHttpTable = TableCat( recvHttpTable, recv )  -- 一時格納用テーブルに受信データを保存
    if #recvHttpTable < 4 then return 0 end  -- 配列の長さを確認

    local head
    local body
    head,body = getHttpHeader( recvHttpTable )  -- 一時格納用テーブルからheaderとbodyに分ける

    if head ~= nil then  -- headerに内容が入っている時
      httpRequest( head, body )  -- HTTPリクエスト処理

      if operationCode == 200 then  -- 処理コード別の処理
        Disconnect()
      elseif operationCode == 400 then
        Disconnect()
      elseif operationCode == 404 then
        Disconnect()
      elseif operationCode == 405 then
        Disconnect()
      else
      end

    else  -- 受信はしたが、HTTP文の全てを受け取った訳ではない場合もある
      return 0
    end
  elseif operationCode == 201 then
    recievedContentTable = TableCat( recievedContentTable, recv )  -- HTTP文の内容格納用テーブルに受信データを保存

    if #recievedContentTable >= contentLength then  -- 全て内容を受信できた場合
      methodPost( recievedUrlStr, recievedContentTable )  -- method postの実行
      Disconnect()
    end
  end

  return 0
end
---------------------------------------------
-- 切断通知
---------------------------------------------
function OnDisConnected()
  Logput(1,'OnDisConnected')
  Disconnect()
  return 0
end

---------------------------------------------
-- http request
---------------------------------------------
function httpRequest( head, body )

  local header = CharFromTbl( head )  -- 引数headの内容を文字列に変換
  local headerLine = strSplit( header, "\r\n" )  -- 一行目を取得
  local line1 = strSplit( headerLine[1], " " )  -- 一行目をスペースで区切る

  local methodStr = line1[1]       -- 最初の単語
  local urlStr = line1[2]          -- 2番目の単語
  local httpVersionStr = line1[3]  -- 3番目の単語
  recievedUrlStr = urlStr  -- 大域変数にも保存

  if strCmp( methodStr, 'GET' ) == 0 then  -- GETの場合
    local fileData = {}
    local mimeType = ""
    fileData, mimeType = methodGet( urlStr )  -- 構文解析して要求されたファイルデータとmime typeを返す
    if fileData ~= nil and mimeType ~= "" then 
      index200( fileData, mimeType )  -- ファイル返信処理
      operationCode = 200  -- 処理コード
    else
      index404()  -- 該当ファイルが見つからない場合
      operationCode = 404  -- 処理コード
    end

  elseif strCmp( methodStr, 'PUT' ) == 0 then  -- PUTの場合
    index405()  -- 該当METHODが許されていない場合
    operationCode = 405  -- 処理コード

  elseif strCmp( methodStr, 'POST' ) == 0 then  -- POSTの場合
    contentLength = 0  -- HTTP文の内容の取り込むべきサイズを一旦0にする
    for i = 1, #headerLine do  -- headerの行数分ループ
      local compStr = "Content-Length:"  -- 探したいのはContent-Length
      if strNCmp( headerLine[i], compStr, #compStr ) == 0 then  -- 文字列の比較 
        local str = strSplit( headerLine[i], " " )  -- スペースで文字列を分割
        contentLength = tonumber( str[2] )  -- 2番目の単語を数値に変換
        break
      end
    end

    if contentLength > 0 then  -- ヘッダーからContent-Lengthを取得できたなら
      if #body > 0 then  -- 引き続きコンテンツが存在する?
        if #body >= contentLength then  -- 全てのコンテンツを受信済み?
          methodPost( urlStr, body )  -- method postの実行
          operationCode = 200  -- 処理コード
        else  -- 全てのデータの受信が完了していないとき
          recievedContentTable = TableCat( recievedContentTable, body )  -- 受信コンテンツ格納用テーブルに追加保存
          operationCode = 201  -- 処理コード
        end
      end
    else  -- 構文解析失敗?
      operationCode = 200  -- 処理コード
    end

  elseif strCmp( methodStr, 'DELETE' ) == 0 then  -- DELETEの場合
    index405()  -- 該当METHODが許されていない場合
    operationCode = 405  -- 処理コード

  else
    index400()  -- 不正なリクエスト
    operationCode = 400  -- 処理コード
  end
end


---------------------------------------------
-- method get
---------------------------------------------
function methodGet( urlStr )
  local file = {}
  local methodTbl = 
  {
    { url = "/",           path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/index.html", path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/index.htm",  path = "D:\\temp\\index.html", mime = "text/html" },
    { url = "/favicon.ico",  path = "D:\\temp\\favicon.ico", mime = "image/png" },
    { url = "/text.txt",  path = "D:\\temp\\text.txt", mime = "text/plain" },
    { url = "/photo1.jpg",  path = "D:\\temp\\photo1.jpg", mime = "image/jpeg" },
    { url = "/image1.png",  path = "D:\\temp\\image1.png", mime = "image/png" },
    { url = "/image2.bmp",  path = "D:\\temp\\image2.bmp", mime = "image/bmp" },
    { url = "/test.csv",  path = "D:\\temp\\test.csv", mime = "text/csv" },
--    { url = "/test.bin",  path = "D:\\temp\\test.bin", mime = "application/octet-stream" },
  }


  for i = 1, #methodTbl do
    if strCmp( urlStr, methodTbl[i].url ) == 0 then
      file = FileRead( methodTbl[i].path )
      return file, methodTbl[i].mime
    end
  end


  return nil,""
end


---------------------------------------------
-- method post
---------------------------------------------
function methodPost( urlStr, contents )
  local path = "D:\\temp\\test"
  local pathLen = #path

  path = path .. strReplace( urlStr, "/", "\\" )
  Logput( 1, path )

  FileWrite( contents, path )
end


---------------------------------------------
-- http request status code = 200
---------------------------------------------
function index200( data, mimeType )
  local contentLength = #data
  local contentHeader = 'HTTP/1.1 200 OK\r\n'
    .. 'Server: SocketeEbugger Web Server/ ver.0.1\r\n'
    .. 'Content-Type: ' .. mimeType .. ' charset=UTF-8\r\n'
    .. 'Content-Length: ' .. contentLength
    .. '\r\n'
    .. 'Connection: close\r\n\r\n'

  SendData( contentHeader )
  SendData( data )
end


---------------------------------------------
-- http request status code = 404
---------------------------------------------
function index404()
  local contentHeader = 'HTTP/1.0 404 Not Found\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>404 Not Found</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Not Found</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- http request status code = 400
---------------------------------------------
function index400()
  local contentHeader = 'HTTP/1.0 400 Bad Request\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>400 Bad Request</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Bad Request</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- http request status code = 405
---------------------------------------------
function index405()
  local contentHeader = 'HTTP/1.0 405 Method Not Allowed\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>405 Method Not Allowed</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Method Not Allowed</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- compare string A and string B.
---------------------------------------------
function strCmp( stringA, stringB )
  local strLen = string.len( stringA )

  if strLen ~= string.len( stringB ) then 
    return -1 
  end

  for i = 1, strLen do
    if string.byte( stringA, i ) ~= string.byte( stringB, i ) then
      return -1
    end
  end

  return 0
end

---------------------------------------------
-- compare string A and string B.
---------------------------------------------
function strNCmp( stringA, stringB, n )
  if n <= 0 then return -1 end

  for i = 1, n do
    if string.byte( stringA, i ) ~= string.byte( stringB, i ) then
      return -1
    end
  end

  return 0
end

---------------------------------------------
-- divide string by ts and enter table.
--  https://qiita.com/peg/items/c472f7a7d9fcca1b5cb7
-- https://symfoware.blog.fc2.com/blog-entry-455.html
---------------------------------------------
--[[
function strSplit(str, ts)
  -- 引数がないときは空tableを返す
  if ts == nil then return {} end

  local t = {} ; 
  i=1
  for s in string.gmatch(str, "([^"..ts.."]+)") do
    t[i] = s
    i = i + 1
  end

  return t
end
]]

function strSplit( str, delim )
    -- Eliminate bad cases...
    if string.find(str, delim) == nil then
--        return { str }
        return nil
    end

    local result = {}
    local pat = "(.-)" .. delim .. "()"
    local lastPos
    for part, pos in string.gfind(str, pat) do
        table.insert(result, part)
        lastPos = pos
    end
    table.insert(result, string.sub(str, lastPos))
    return result
end


---------------------------------------------
-- ASCIIテーブルをコード変換後に文字列化
--
-- ASCIIの例
-- 入力: { 0x30, 0x31, 0x32 }
-- 返値: "012"
--  http://amlaid.air-nifty.com/blog/2015/10/serial-debugger.html
-- 0x0D is CR,0x0A is LF
---------------------------------------------
function CharFromTbl(tbl)
  tblChar = {}

  for i=1, #tbl do
    if tbl[i] == 0x0D or 
       tbl[i] == 0x0A then
      tblChar[i] = string.char(tbl[i])
    else
      tblChar[i] = string.char(tbl[i])
    end
  end

  return table.concat(tblChar)
end


---------------------------------------------
-- replace string to tr from ts and enter table.
---------------------------------------------
function strReplace( str, before, after )
  local str,num = string.gsub( str, before, after )
  return str
end



---------------------------------------------
-- get http header from table
-- CR is 0x0D LF is 0x0A
---------------------------------------------
function getHttpHeader( tbl )
  local header = {}
  local body = {}
  local tblLen = #tbl
  local index = 1

  for i = 1, tblLen - 3 do
    if tbl[ i + 0 ] == 0x0D and
       tbl[ i + 1 ] == 0x0A and
       tbl[ i + 2 ] == 0x0D and
       tbl[ i + 3 ] == 0x0A then
       break
    else
      header[i] = tbl[i]
      index = index + 1
    end
  end
  if index > tblLen - 3 then return nil,nil end

  for i = 1, tblLen do
    body[i] = tbl[ i + index + 3 ]
  end

  return header,body
end

※、わたしあれ、正規表現さっぱりですわ!








今更ルネサス SH2-7045にプログラムを書き込む!

$
0
0
SH2 7045は、ルネサス提供の書き込みツールであるFDTすらサポートされていないくて、、、( ノД`)シクシク…

なので今迄はHtermで書き込みしていましたが、さすがにHtermの書き込みはキツイ!(OSとか、USBシリアルとの相性が有る?)ので、コマンドラインの書き込みツールを作った。

Windows10上のBCC32Cで開発。Windows10とXPでやってみたが、ちゃんとXPでも動くのだね~。

書式は
shwriter COM10 w7045s.mot USERPROGRAM.mot

と言う訳で置いておきますので、試しにどうぞ。うまく行った!とか、全然ダメ!とか、報告を是非。
https://1drv.ms/u/s!AgxfaDqma1yrhkrnI3M3g5FcizyO


※添付してあるファイルの、exeファイルが書き込みプログラム。w7045s.motはSH2のRAM上で動作するユーザープログラムのローダー。mon0229.motはユーザープログラム例(SH2 7045用のデバックモニター)
※モトローラSフォーマットのみ対応

M5StackとMLX90640

$
0
0
IoTLTで登壇を申し込みました。
https://iotlt.connpass.com/event/124544/?fbclid=IwAR20sd0yigJlk1trNuqP-CdMaMHmnPK10GJ8I9jpyvdpOI_juDaeaEmuHhY
m5stack_mlx90640_004.jpg表示させてみた。MLX90640はマルツで税込で6000円弱で購入可能。実際にはデジキにオーダーされる!


MLX90640のメリットは、FLIRと比較して安価で入手が容易な事、測定温度範囲が広い事。
デメリットはFLIRと比較して解像度がだいぶ低い事。
FLIRをデジキで買った時はたいへんだった。まずアメリカの輸出規制に引っ掛かるので、アメリカ政府に軍事目的じゃなく民間利用ですよ~って申請書類を書かねばならない。しかも実際に輸出されるまで長い事、長い事。忘れた頃に発送の通知が来る。たぶん今、アメリカ政府の機関が予算の関係で動いていないので、もっと時間掛かるに違いない!

kicadのプロジェクト(回路図込み)
https://1drv.ms/u/s!AgxfaDqma1yrhkzz561J0Jj7Bng5
と、とりあえずサーモグラフィー表示をするソースコード
https://1drv.ms/u/s!AgxfaDqma1yrhkzz561J0Jj7Bng5
部品表
https://1drv.ms/x/s!AgxfaDqma1yrhk3DE9TKJ_fgpOgd
を公開しました。
省電力でも使える?ように3.3Vと5V電源を操作できるようにしてあります。

MLX90640のライブラリはgitHubで公開しているライブラリを利用しています!(ソースの先頭にライセンスを貼り付け)

基板はPCBGOGOで注文すれば、凄く安く、かつ早く作ってくれます。

基板に注意点があります。
1.うっかりしていましたが、5V電源のFETスイッチのR17は抵抗ではなく青色LEDを実装してください。基板下側がカソードです。

2.LED1、LED2も基板下側がカソードです。

3.MLX90640は円柱形の根本のタブが下側になります。

※こう言うのをWEBブラウザ上で背景の写真とオーバーレイで表示するJava scriptとか知っていたら教えて欲しい。
m5stack_mlx90640_001.jpg

m5stack_mlx90640_002.jpg二枚目は液晶テレビを撮影。右側に電源が有るのかな?

m5stack_mlx90640_003.jpg


M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー


M5Stackとフォント

$
0
0
m5stack_proportional_font_003.jpg
M5Stackの標準ライブラリでprintメソッドを使った場合、結構文字が小さく表示されます。
しかしライブラリのソース(In_eSPI_Setup.h)を見てみると実際に利用可能なフォントは複数あります。

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

LOAD_GLCDがおそらくprintメソッドで利用されるフォントです。上記の写真の上半分の文字がそれです。小さいですね。
LOAD_FONT2以降をprintメソッドから利用する方法が見つかりません。drawCentreStringやdrawRightStringからは引数に数字でフォントを指定すれば画面上に表示されます。
しかしdrawCentreStringやdrawRightStringはラベル等で利用する事を目的としたのか、改行は利きませんので、長文はうまく行きません。

LOAD_FONT2のフォント(Font16.c)のみ、画面に出力するライブラリを作成してみました。
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb

他のフォントはRLE(RUN LENGTH ENCODING)されており、ちょっとデコードの方法が判らなかったので対応していません。
標示した長文が一番上の写真の下側の黄色い文字です。文章が改行されている事が判ります。

これらのフォントはプロポーショナルフォントの様です。比較的見栄えの良い表示が出来ていると思います。

M5Stackのソースには上記プロポーショナルフォント以外に等幅フォントがありました。5×7のフォントで、表示させてみたのが下の写真です。

ところで標準のprintライブラリってこっちが作成したライブラリに比較して文字の表示が圧倒的に速いじゃないですか、あれ、ソースコードを見てみると、直接SPIでLCDにビットデータを転送しているのね!
m5stack_proportional_font_001.jpg


M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack用ミニサーマルカメラユニット

M5Stack用ミニサーマルカメラユニット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー


M5Stackとフォント ランレングス(RUN LENGTH ENCODING)への対応

$
0
0
m5stack_proportional_font_004.jpg前回ではランレングスのデコード方法が判らなかったので、非圧縮フォントデータのみ扱いましたが、今回はデコード方法が判ったのでそれに対応しました。

一応フォントデータのデコード方法ですが、例えばFont32rle.cを見てみると
widtbl_f32[96]と言う配列、chr_f32_20[]と言う配列、chrtbl_f32[96]と言う配列が有る訳ですが、

widtbl_f32はその文字の横幅を示しています。最初の値はASCIIコードでスペースなのですが、そのスペースの横幅はこの場合5ピクセルです。

chr_f32_20は文字のビットパターンで、ここがランレングスで圧縮されています。配列名の最後の2文字はASCIIコード番号を示し、この場合はスペースの20(16進)です。

chrtbl_f32は上記ビットパターンのポインターの配列です。

なので文字の横幅を知りたい時は、ASCIIコード番号からオフセット分を引いた値をwidtbl_f32配列のインデックスにすればアクセスできますし、ビットパターンを知りたい時はASCIIコード番号からオフセット分を引いた値をchrtbl_f32配列のインデックスにすればアクセスできます。

ランレングスのデコードですが、ビットパターンのそれぞれの1byte単位のデータに着目してみます。
例えばASCIIコードでスペースの値は以下です。
PROGMEM const unsigned char chr_f32_20[] = 
{
  0x7F, 0x1
};

1byteのデータの最上位ビットが色の情報であり、例えば最上位に1が立っていれば1ピクセル表示する。1が立っていなければ1ピクセルの表示は行わないとします。(逆でも構わない)
続く7bitはその色情報が続く長さを示し、0から127までの値を取りえますが、実際はその値に1を加算します。
上記の0x7F, 0x1であれば、127+1+1+1=130ピクセル分表示しない事になります。
さて横幅は5ピクセルですので、130を5で割れば26となり、26は文字高さになります。
実際はこの値はFont32rle.hの中で定義されていますので、この定義を利用する方が良いでしょう。

さて、ランレングスのデコード方法が判ったので、実際にLCDに表示させてみました。
フォントサイズが32まではそれなりにASCIIコードが表示されますが、それ以上のサイズのフォントはまともに表示できるのは数字のみの様です。
ROMサイズをケチったのか?それとも面倒臭かったのか?

ESP32 Arduinoのプロジェクトを置いておきます。
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb


※追記
M5Stackの組み込みフォントのヘッダーファイル、例えばFont32rle.hは再帰呼び出しに対応していないので、ファイルの先頭と最後に以下の様な#ifdef文を追加します。Font32rle.hの例。
#ifndef FONT32RLE_h
#define FONT32RLE_h

#include 

#define nr_chrs_f32 96
#define chr_hgt_f32 26
#define baseline_f32 19
#define data_size_f32 8
#define firstchr_f32 32

extern const unsigned char widtbl_f32[96];
extern const unsigned char* const chrtbl_f32[96];

#endif  /*FONT32RLE_h*/



M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack用ミニサーマルカメラユニット

M5Stack用ミニサーマルカメラユニット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stackとフォント GFXFFへの対応

$
0
0
m5stack_proportional_font_005.jpg前回ではランレングスに対応しました。今回はGFX Free Fontと言うフォントの対応です。
GFX Free Fontもプロポーショナルフォントですが、特に筆記体を表示するのに向いている様な気がします。

マイコンにとって最も処理の負荷が小さそうなのは等幅フォントですが、等幅フォントでは例えば小文字の'i'が連続した時に、'i'と'i'の間が大きく開いて体裁が良くありません。
プロポーショナルフォントなら文字毎に適切な文字間隔を指定できるので、とても体裁が良くなります。しかし制御が増えるのでその点は良し悪しです。
ランレングスはSPIとは相性が良さそうな気がしますね。



GFXフォントの使い方の解説(本家?)はここから。
https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts

Customフォルダーの中のフォントは以下のサイトで生成された物?
http://oleddisplay.squix.ch/#/home

GFX Free Font(GFXFF)を使う為のもっとも簡単な方法は、すでにM5ライブラリに用意されているメソッドを使う事です。
例えば
setFreeFont( GFXFFにアクセスする為の構造体のポインタ );でこれから使用するフォントを選択。
drawString( テキスト, 横位置, 縦位置, フォント番号 );で指定された位置に先に指定されたフォントでテキストを描画します。まあつまりこんな感じで、
  M5.Lcd.setFreeFont( &Yellowtail_32 );  // Select the font
  M5.Lcd.drawString( "hello world.", 0, 0, 1 );

これらメソッドはM5Stack.hには記載されていませんが、M5Display.hの中でTFT_eSPIクラスが継承されているのでIn_eSPI.hを参考にすると良いでしょう。
しかし相変わらずこのメソッドでは画面の右端に行った時に折り返しをしてくれません、、、よね?

折り返しとか改行とかを行いたいなら、自分で描画メソッドを作るしかない?
以下ではその解説を!

1.GFXフォーマットのフォントデータファイルは3つの要素で構成されている。

1) 文字をビットマップデータ化したデータ領域
Yelllowtail_32.hであれば
const uint8_t Yellowtail_32Bitmaps[] PROGMEM
配列名は任意でこのファイルの中でしか利用されないが、static宣言はされていない。

2) 該当文字の属性データ。例えば該当文字のデータが始まる位置を、上記ビットマップデータ
の先頭からのオフセットとか、文字の横サイズ、文字の縦サイズ、次の文字までのピッチ、
描画原点からの横と縦のオフセット等が配列化されている。

3) ビットマップデータ、上記属性データ、行間のピッチをまとめた物。
文字を表示する場合はこの変数をアクセスする事となる。

2.GFXフォーマット
gfxfont.hの中で2つの構造体が定義されている。

1) GFXglyph
    typedef struct { // Data stored PER GLYPH
      uint16_t bitmapOffset;     // Pointer into GFXfont->bitmap
      uint8_t  width, height;    // Bitmap dimensions in pixels
      uint8_t  xAdvance;         // Distance to advance cursor (x axis)
      int8_t   xOffset, yOffset; // Dist from cursor pos to UL corner
    } GFXglyph;

Yelllowtail_32.hの中の文字'A'のGFXglyphレコード
{ 1141, 24, 24, 20, 1, -23 }, // 'A'

bitmapOffset : ビットマップデータ領域の先頭からのオフセット。この場合は1141。

width, height : 文字の横幅 = 24ピクセル。高さ = 24ピクセル。

xAdvance : 次の文字までの横方向のピッチ = 20

xOffset, yOffset : 文字の描画開始位置からの横方向のオフセット = 1。
文字の描画開始位置からの縦方向のオフセット = -23。

2) GFXfont
    typedef struct { // Data stored for FONT AS A WHOLE:
      uint8_t  *bitmap;      // Glyph bitmaps, concatenated
      GFXglyph *glyph;       // Glyph array
      uint8_t   first, last; // ASCII extents
      uint8_t   yAdvance;    // Newline distance (y axis)
    } GFXfont;

Yelllowtail_32.hの中のGFXfontレコード
const GFXfont Yellowtail_32 PROGMEM = {
(uint8_t *)Yellowtail_32Bitmaps,(GFXglyph *)Yellowtail_32Glyphs,0x20, 0x7D, 45};

bitmap : ビットマップデータ全体へのポインタ

glyph : GFXglyph構造体の配列のポインタ

first, last : 最初の文字、最後の文字。ASCIIコードからfirstを引いた値を
GFXglyph構造体配列のインデックスとする。

yAdvance : 次の行までの高さ。


3.GFXフォーマットの文字データの展開
該当文字のGFXglyph構造体レコードを取得すると、該当文字データの開始アドレスを
取得できる。
このデータはランレングス(RUN LENGTH)の様な圧縮は行われておらず、ビット列を
ビットマップとして画面上に展開する。しかしFont16.cの様な無圧縮のビットマップ
データをそのままメモリ領域に展開してしまうと文字サイズが大きい時は必要なメモリ
領域も大きくなってしまうので文字の描画開始位置を指定する事でメモリサイズの
増大を防いでいる。
例えば'A'であれば文字の上の辺りから描画開始しなければならないが、'.'であれば
随分下の位置からの描画開始となる。
以下はYelllowtail_32.hの文字'A'と文字'.'のデータである。

文字'A'のビットマップデータ
0x00,0x00,0x1E,0x00, 0x00,0x3F,0x00,0x00,
0x7E,0x00,0x01,0xEE, 0x00,0x03,0xDC,0x00,
0x07,0x9C,0x00,0x0F, 0x38,0x00,0x1E,0x38,
0x00,0x1C,0x78,0x00, 0x38,0x70,0x00,0x70,
0x70,0x00,0xE0,0xE0, 0x01,0xE0,0xE0,0x03,
0xC1,0xC0,0x7F,0xFF, 0xC0,0x7F,0xFF,0x80,
0x0E,0x03,0x80,0x1C, 0x07,0x00,0x38,0x07,
0x00,0x78,0x0F,0x00, 0x70,0x0E,0x00,0xE0,
0x1E,0x00,0xE0,0x1C, 0x00,0xC0,0x38,0x00, // 'A'
でかい!

文字'.'のビットマップデータ
0x7F,0xE0, // '.'

文字によって横幅、高さ、描画開始位置等の属性が異なり、文字を描画する為には
その情報にアクセスしなければならない。

文字'A'の属性データ
{ 1141, 24, 24, 20, 1, -23 }, // 'A'

文字'.'の属性データ
{ 431, 4, 3, 9, 2, -2 }, // '.'

文字'A'のxOffsetは1、yOffsetは-23。
文字'.'のxOffsetは2、yOffsetは-2。

xOffsetは文字開始位置からの相対位置、yOffsetはyAdvanceからの相対位置となる。
基本的にM5stackの描画は左上から右下方向に開始され、Xが大きくなれば右に、
Yが大きくなれば下に進む。
それぞれの文字の描画開始位置は横方向ならxOffsetから、
縦方向なら yAdvance + yOffset(注1)から開始する。Yelllowtail_32.hのyAdvanceは45である。

お試しプログラムは以下のリンク
https://1drv.ms/u/s!AgxfaDqma1yrhk6AY-NUsBh7pyXb

(注1) とか言いつつソースには高さ方向で調整が入っているじゃあないかい!

※TFT_eSPIクラスにはreadPixelメソッドが存在するので、ピクセルの読み出し可能か?と期待しましたが、見事にFFFFが返ってくるorz

M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack用ミニサーマルカメラユニット

M5Stack用ミニサーマルカメラユニット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー


M5Stackとフォント 漢字フォントの対応

$
0
0
2019-03-16 11.10.07.jpgFONTX形式の漢字フォントの表示。但しShift-JIS配列で漢字フォントデータが構築されているので、UTFでは表示できない!

FONTX形式の解説はCHAN氏がされているので、そこを参考にしてね。
http://elm-chan.org/docs/dosv/fontx.html

このブログでも過去記事か有ります。
https://hamayan.blog.so-net.ne.jp/2009-09-19-1
※本当はもっと詳しく書いた記事が有ったのですが、失われた、、、

今回使用しているFONTX形式のフォントデータは力武健次氏が公開されている物を利用させていただきました。もうだいぶ昔の事なので改めて氏のリンク先を探したら、以下のページが有りました。
http://www.k2r.org/gijyutsushi

なぜFONTX形式?
実際のところM5Stackが流行るずいぶん前から組込みで液晶に日本語を表示する事は広く行われてきました。その液晶表示する時に比較的扱い易いフォントデータ形式としてFONTX形式がよく使われています。他にもBDF形式とかですね。
なので、「FONTX形式」で検索してもらえれば、沢山の利用例が見つかります。

なぜShift-JIS?
上記の日本語表示の流行の頃は日本語のコード体系ではShift-JISが一般的だったのです。今でもShift-JIS配列のFree fontを多く見つけられるでしょう。

例えば16×16ドットのフォントで第2水準までサポートした場合のROM容量は250kbyteくらいです。昔のマイコンならいざ知らず、ESP32とかなら組み込んでも余裕ですね!

但し!
現在主流のコードはUTFです。Arduino IDEで日本語を書いた時、そのコードはUTFになっています。UTFのコードをそのままShift-JISのコードに変換できず、これが結構面倒です。組み込みには厳しい状況です。

同じ日本語( 覇漏倭亞琉弩、夜露死苦 )をShift-JISとUTF-8で書いた物をバイナリーエディターで開いた物が以下です。
m5stack_proportional_font_006.png
上がShift-JIS、下がUTF-8N


Shift-JISの文字コードは16bit固定長ですが、UTFでは拡張性?からか8bit~の可変長です。なのでこの場合はデータ量が増えていますね。

さて、UTFのコードからShift-JISのフォントデータにアクセスする方法、どうしましょう?

M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack用ミニサーマルカメラユニット

M5Stack用ミニサーマルカメラユニット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー


M5Stackとフォント 漢字フォントの表示 UTF8への対応 #m5stack

$
0
0
おおむね成功ですっ!ちぃ、、ですっ!
m5stack_utf8_001.jpgさて前回は16×16ドットのフォントでJIS第2水準程度の日本語を表示する。ただし表示できるのはShift-JISコード!と言う内容でしたが、今回はUTF8まで対応してみます。
UTF8でJIS第2水準程度をサポートしていれば、日本語の表示ではそれほど苦労しないで済みそうですね。

前回書いた様にここに有るのはShift-JISに対応した漢字のフォントデータ。しかし対応しなければならないのはUTFのコード。
ならばUTFのコードをShift-JISに変換するしかない!

と言う訳で、ここではその手順を書いてみます。ちなみにこれがベストの方法だとは思いませんが、このブログは基本的に備忘録なので。

1.FONTX形式のファイルのShift-JISコードだけの(つまりここではビットマップデータは必要無い)リストを作る。※PCで作業

2.上記リストのコードを16進表記した値(文字列)と、そのコードをバイナリー化した値をレコードとしたファイルを生成する。
以下の様なファイルですね。これはShift-JISのファイルとなります。このファイルをエディタで開くとバイナリー化した値は文字として読めます。※PCで作業
0x889F,亜;  // 亜
0x88A0,唖;  // 唖
0x88A1,娃;  // 娃
0x88A2,阿;  // 阿
0x88A3,哀;  // 哀


3.このファイルをエディタの機能を使ってUTF8に変換する。以下の様になりますね。見た目はまったく変わりませんが(笑)、漢字はUTF8のコードに直っています。つまり1行毎に16進表記のShift-JISのコードとUTF8のコードの対応が取れました。※PCで作業
0x889F,亜;  // 亜
0x88A0,唖;  // 唖
0x88A1,娃;  // 娃
0x88A2,阿;  // 阿
0x88A3,哀;  // 哀


4.上記ファイルのUTF8のバイナリーデータを16進表記に変換して、構造体の配列としてCソースファイルを生成します。この時検索性を良くするためにUTF8のバイナリーデータを昇順で配列の並べ替えを行って置きます。
以下の様なファイルを作ります。この場合UTF8のコードは4byte長としています。UTF8のコードは最長で6byteとなるようですが、今回変換した漢字コードは最大でも3byteしか使っていませんでしたので、4byte長で充分だと思います。目的は手持ちのShift-JISコードのフォントデータを利用するだけですから。※PCで作業
const struct SJIS_UTF8_TABLE
{
  unsigned short sjis;
  unsigned long  utf8;
} sjis_utf8_table[] =
{
  {0x8198,0x0000C2A7},
  {0x814E,0x0000C2A8},
    ・
    ・
    ・
  {0xFA55,0x00EFBFA4},
  {0x818F,0x00EFBFA5},
};
const int sjis_utf8_table_number = 8127;


5.上記変換データを収めたCソースファイルをプロジェクトに取り込む。

6.文字列を先頭から1byte単位で読み出し、最上位bitが立っていれば漢字、立っていなければASCII文字として処理します。※マイコンで作業

7.漢字であれば上位bitのパターンから後ろに何byte続くか判断し、それらを32bit長の変数に代入し、その値をキーに変換テーブルから該当するShift-JISコードを取得します。※マイコンで作業

8.後は前回の漢字表示処理を行うだけです。※マイコンで作業

プロジェクト一式


※今回の漢字をマイコンで表示させる手順としては、別にM5stackに限らず様々なマイコンで同じ方法が使えると思います。
※しかしフォントデータと変換データを合わせて、かなりの量のROM容量が必要ですね!

参考
ウィキペディア https://ja.wikipedia.org/wiki/UTF-8
今日もスミマセン。
http://d.hatena.ne.jp/snaka72/20100710/SUMMARY_ABOUT_JAPANESE_CHARACTER_CODE


M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack Basic

M5Stack Basic

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー



M5Stack用ミニサーマルカメラユニット

M5Stack用ミニサーマルカメラユニット

  • 出版社/メーカー: スイッチサイエンス
  • メディア: おもちゃ&ホビー


STM32FをArduino iDEで開発的なメモメモメモメモ

$
0
0
ハードディスクが飛ぶ寸前だったのを機にSSDに交換してクリーンインストールしたので、ついでにArduino IDEでstm32duinoのArduino Core STM32に手を出している。

https://github.com/stm32duino

環境設定から追加のボードマネージャーのURLを追加し、
https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json

ボードマネージャーでArduino Core STM32を追加したが、まぁいろいろうまく行かなかった。
ボードマネージャーで追加されたツールチェインでは正常にコンパイルが行われなかったので、gnu-armの最新版、いわくつきの2018年版をダウンロードしてplatform.txtでツールチェインのパスを変更

20行目辺り
#compiler.path={runtime.tools.arm-none-eabi-gcc-6-2017-q2-update.path}/bin/
compiler.path={runtime.tools.arm-none-eabi-gcc-8 2018-q4-major.path}/bin/
2019-05-08.png


が、今度はobjcopyでよくわからんエラーで先に進まない。

どうやらobjcopyのバグらしくこんな報告が上がっている、、、
https://bugs.launchpad.net/gcc-arm-embedded/+bug/1810274

はぁ、、、(*´Д`)

対策は、2018年版のツールチェインをやめて2017年版にするか、2017年版のobjcopyだけを上書きコピーすること。
※結局他でもトラブル起こすので、2017年 Q4 メジャー版に戻しましたよ、、、はぁ

お役立ちリンク
https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=2ahUKEwiAgI2ywoviAhULxbwKHbnFD3oQFjAAegQIARAB&url=https%3A%2F%2Fscrapbox.io%2FArduinoSTM32%2FArduino_STM32_%25E3%2583%25AA%25E3%2583%2595%25E3%2582%25A1%25E3%2583%25AC%25E3%2583%25B3%25E3%2582%25B9_%25E6%2597%25A5%25E6%259C%25AC%25E8%25AA%259E%25E7%2589%2588&usg=AOvVaw23b9dYNx5_EZ6Z6loRNkAL

Socket Debuggerを使ってみる! ポート:1!

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!


ネットワークデバイスのデバックで最近利用を始めたSocketDebugger、無償版と有償版があります。
有償版に比較して無償版ではいくつかの機能が制限されますが、それでもとても有用なツールだと感じています。
特に、標準で備わっている機能だけでもデータの送受信を自動的に行うなどは容易に設定してできますが、Luaスクリプトを組むことでより詳細な動きを制御できます。

無償版は基本的な設定項目を保存することができませんが、Luaスクリプト自体はユーザーがエディタで作成し、保存、読み出しができますので、なるべくLuaスクリプトで処理をした方が同じようなデバックを何度も行うなら楽だと思います。


Lua?
ウイキペディア https://ja.wikipedia.org/wiki/Lua

参考にしているサイト
https://qiita.com/rohinomiya/items/abeb1d69c640a27d97c5

https://qiita.com/dwarfJP/items/e033728f5cbecbabe11f

http://amlaid.air-nifty.com/blog/2015/10/serial-debugger.html

https://qiita.com/satorimon/items/d65872659f7d6dc453fd

SocketDebuggerは2つのポート(ポート1とポート2)が同時に操作できますんで、わざわざターゲットを用意しなくてもSocketDebuggerだけでお試しやスクリプトの実行を行うことができます。

以下例ではポート1をTCPのエコーサーバー、ポート2をTCPのユーザークライアントだとします。
port1_connection_config_001.pngポート1の通信設定->接続の項目:TCPサーバー、IPアドレス127.0.0.1は自分自身を指定、ポート番号を1024で起動します。


port2_connection_config_002.pngポート2の通信設定->接続の項目:TCPクライアント、IPアドレス127.0.0.1は自分自身を指定、接続先ポート番号を1024で起動します。localポート番号を0にしておけば、適宜接続の度に適当なポート番号を割り当てるのでしょう。


port1_actions_config_003.pngポート1の通信設定->動作の項目:5000ms以内にデータの受信が無い時は切断します。また、受信時動作は受信データをそのまま返します。


port2_actions_config_004.pngポート2の通信設定->動作の項目:スクリプトで制御を行います。


スクリプト2のタブを開けばエディタが開き、スクリプトを編集できます。
標準の内容はそれぞれ発生したイベントに対する動作となっています。コメントはこちらで付けました。
---------------------------------------------
-- 接続完了通知
---------------------------------------------
function OnConnected()  -- 相手先と接続すると呼ばれるハンドラ
    Logput(1,'OnConnected')  -- 詳細ログデータ画面にメッセージを表示
    return 0
end
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()  -- 送信ボタンをクリックすると呼ばれるハンドラ
    Logput(1,'OnSendPush')
    a = GetEditorData()  -- 送信データエディタ上の内容を変数に代入
    SendData(a)  -- 変数の内容を送信
    return 0
end
---------------------------------------------
-- タイマー通知
---------------------------------------------
function OnTimer(id)  -- タイマーで設定した時間に呼ばれるハンドラ
    Logput(1,'OnTimer')
    return 0
end
---------------------------------------------
-- 受信通知
---------------------------------------------
function OnReceive(recv)  -- 相手から受信した時に呼ばれるハンドラ、引数のテーブル(recv)に受信データが格納されている
    Logput(1,'OnReceive')
    return 0
end
---------------------------------------------
-- 切断通知
---------------------------------------------
function OnDisConnected()  -- 切断された時に呼ばれるハンドラ
    Logput(1,'OnDisConnected')
    return 0
end

今回はポート2の送信ボタンが押された時の動作、function OnSendPush()を使う訳ですが、送信データエディタ上の内容をそのまま送信するならこのままで大丈夫です。
※送信データエディタ上に何もデータが無ければ、送信動作は行われません。

transmit_data_editor_005.pngエディタ上ではバイナリーデータですが、テキスト入力機能を使えばテキストを扱えます。


transmit_data_editor_006.png


これで準備が整いました。まずポート1のTCPサーバーを通信開始ボタンをクリックして起動します。画面にSocket処理開始と表示されている筈です。IDが1なのはポート1を示しています。

ポート2のユーザーTCPクライアントを通信開始ボタンをクリックして起動します。画面上にID:2のイベントが複数表示されます。何もしなければ5秒後に向こうから切断されてしまいます。

もう一度ポート2の通信開始ボタンで起動し、そのボタンの右隣のデータ送信ボタンをクリックしてみます。

echo_actions_007.png左上の画面で一連の送受信の様子や接続、切断処理が判ります。まずはこんな具合につこうてみて下さい。


echo_actions_008.png成功しているようです。








Socket Debuggerを使ってみる! ポート:2! 受信データをチェックしてみる

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

前回ではエコーサーバーに対してユーザークライアントが適当なデータを投げて、サーバーから返ってきたデータを受信するまでをやりました、、、うーん受信したデータは大丈夫?って疑念が湧きますよね普通。

なので今回はスクリプトを自作して送信データと受信データの比較をしてみます。
以下の様なテーブル同士を比較するユーザー関数を作成してみました。ポート2のスクリプトファイルの一番最後にでも置いておきます。
---------------------------------------------
-- compare table A and table B.
---------------------------------------------
function compareTbl( tableA, tableB )
  local tableALen = #tableA  -- テーブルAの要素数を変数に代入
  local tableBLen = #tableB  -- テーブィBの要素数を変数に代入
  if tableALen ~= tableBLen then return -1 end  -- 要素数を比較して違うならエラーで帰る

  for i = 1, #tableA do  -- テーブル(配列)は1から始まり、要素数までループ
    if tableA[ i ] ~= tableB[ i ] then return -1  -- 要素同士を比較して違うならエラーで帰る
    end
  end

  return 0
end

微妙な違いですが送信ボタンのイベントの変数名を変えました。変数はlocal修飾子を付けないとグローバル変数扱いです。
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
    Logput(1,'OnSendPush')
    sendData = GetEditorData()
    SendData( sendData )
    return 0
end

受信イベントを以下の様に変えました。
テーブル同士を比較してログ出力メッセージを変えています。
---------------------------------------------
-- 受信通知
---------------------------------------------
function OnReceive( recv )
  Logput( 1, 'OnReceive' )
  if compareTbl( sendData, recv ) == 0 then
    Logput( 1, 'Receive data was good.' )
  else
    Logput( 2, 'Receive data was not good.' )  -- 引数に2を指定すると注意、1なら情報、3なら警告
  end

  return 0
end


echo_actions_008.png成功しているようです。








Socket Debuggerを使ってみる! ポート:3! 送信データをスクリプトで生成

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

前回では送信データは送信データエディタ上のデータを使っていましたが、今回は自分でデータを作成して送信、受信、比較を行います。データはランダムな値です。
function OnSendPush()を以下の様に改造します。
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
  Logput(1,'OnSendPush')

--  sendData = GetEditorData()

  -- ランダムなデータを生成する
  sendData = {}  -- 空のテーブルを作成する
  for i = 1, 1024 do  -- 1024個のデータを生成
    sendData[ i ] = math.random( 255 )  -- 0から255までのランダムな整数を生成
  end
  SendData( sendData )
  return 0
end



echo_actions_009.png成功しているようです。










Socket Debuggerを使ってみる! ポート:4! タイマーを使って自動化?

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

前回ではぽちぽちボタンを押すと言うアナログな送信でしたが、今回はタイマーを使って半自動化を行います。
まずfunction OnConnected()を以下の様に改造します。現在のタイマーの状態を保持する変数の生成と初期化です。
---------------------------------------------
-- 接続完了通知
---------------------------------------------
function OnConnected()
  Logput(1,'OnConnected')

  timer1StartIs = false  -- timer1の起動状態を記憶する変数を論理型で生成する

  return 0
end


function OnSendPush()を以下の様に改造します。ここでは送信は行わず、タイマーの制御を行います。
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
  Logput(1,'OnSendPush')
--  sendData = GetEditorData()

  if timer1StartIs == true then
    timer1StartIs = false
    KillTimer( 0 )  -- timer1を停止する
  else
    timer1StartIs = true
    SetTimer( 0, 1000 )  -- timer1を1000ms周期で起動する
  end
  return 0
end


function OnTimer(id)を以下の様に改造します。引数idはタイマーidが入っていますが、まあ今回はtimer1しか使っていませんので、、、
---------------------------------------------
-- タイマー通知
---------------------------------------------
function OnTimer(id)
  Logput(1,'OnTimer')

  -- ランダムなデータを生成する
  sendData = {}  -- 空のテーブルを作成する
  for i = 1, 1024 do  -- 1024個のデータを生成
    sendData[ i ] = math.random( 255 )  -- 0から255までのランダムな整数を生成
  end
  SendData( sendData )

  return 0
end



echo_actions_010.png成功しているようです。









Socket Debuggerを使ってみる! ポート:5! あまり受信頻度が高いと、、、

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

前回ではタイマーを使って半自動化を行いましたが、今回はローカル(127.0.0.1)相手ではなく自作したネットワークデバイスを相手にして、タイマー周期を短くしてみました。
これですね!
SetTimer( 0, 100 )

echo_actions_011.png200ms周期くらいまでは比較的ちゃんと動いていたような気がしますが、100ms周期となると結構厳しい感じで、処理が遅れている様子が見て取れます。
例えばこの図では送信パケットのデータサイズが毎回1024byteであるのに対して受信パケットのサイズが1024以外の値を示す事が頻発します。また、停止ボタンなども効き辛くちょっと暴走気味です。


echo_actions_012.png192.168.50.21が相手デバイスで、192.168.50.254がPCです。
LANアナライザーでは送受信ともに1024byteで行われています。これはSocketDebuggerか?、OSか?どちらかの内部処理の遅延が原因なのか、受信バッファの内容を複数パケット分処理している為に起きていると思われます。


まぁ何かしら限界も有るという事で、その辺も踏まえて使う必要が有るんじゃないかと。
※Windowを最小化して動かしている間は、タスクマネージャーのCPU負荷率はだいぶ減るので、可能ならばそれも有りなんではないかな。








Socket Debuggerを使ってみる! ポート:6! レコードを送りたい!

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

※SokcetDebuggerはビット演算を行う拡張関数が有りました!


通信で値を集めたレコードを送受信したい等という事はよくある要求じゃあないですか、特に業務辺りなら。
なのでレコード、C言語であれば構造体に値を代入してまるっと投げちゃうあれができるかどうかをやってみます。
送信処理のところを以下にしてみます。
  -- レコードデータを生成する
  sendData = {0,1,2,3,4,5,0.12345,'designed by hamayan.'}
  SendData( sendData )

つまり整数、実数、文字列がどうなるのか?
record_transmit_013.pngうーむ、最初の6個のデータはbyte型として送られ、浮動小数点型は0しか送られず、それ以降のデータは削除のようです。


record_transmit_014.pngLANアナライザー上でも7byteしか認められません。


さてさて、困ったねぇ、、、C言語の様に型が明確でないと変換どうするのかねぇ、、、

追記
結局のところこのSocket DebuggerでサポートしているLuaのver.5.1では数値は実数(double)であり、大きな数値ほど誤差が大きくなる。と言うか変数をネットワーク上に流すためにbyteデータに変換しようとしても正確に変換できない。
例えばある変数の値に-1を代入したとして、それが64bitの変数を期待している場合、ユーザーは16進数で
0xFFFFFFFFFFFFFFFF
を期待すると思うが、実際には誤差でオーバーフローして0x0000000000000000となってしまう、、、
---------------------------------------------
-- convert int64 to byte array by big endian.
-- 数値は実数(double)型である為、例えば64bitで-1は正しく変換できない
---------------------------------------------
function ConvertInt64ToByteArrayBig( value )
--  value = value % 2 ^64

  local tbl = {}

  if value < 0 then 
    value = 2^64 + value 
  end

  tbl[ 1 ] = value / 2 ^ 56
  value = value % 2 ^ 56

  tbl[ 2 ] = value / 2 ^ 48
  value = value % 2 ^ 48

  tbl[ 3 ] = value / 2 ^ 40
  value = value % 2 ^ 40

  tbl[ 4 ] = value / 2 ^ 32
  value = value % 2 ^ 32

  tbl[ 5 ] = value / 2 ^ 24
  value = value % 2 ^ 24

  tbl[ 6 ] = value / 2 ^ 16
  value = value % 2 ^ 16

  tbl[ 7 ] = value / 2 ^ 8
  value = value % 2 ^ 8

  tbl[ 8 ] = value

  return tbl
end


さらにLuaのver.5.1ではビット演算子もシフト演算子もサポートしていないもの痛い。Luaのver.5.3では64bit整数もビット演算子もサポートされている様な気がする。

Luaのver.5.3リファレンスマニュアルより
「数値型には2種類の内部表現 (あるいは2つのサブタイプ) があります。 ひとつは整数で、もうひとつは浮動小数点数です。 それぞれの表現がいつ使われるかについては明確なルールがありますが、両者は必要に応じて自動的に変換されます (§3.4.3 を参照)。 そのためプログラマは整数と浮動小数点数の違いをほとんど無視することもできますし、各数値の表現について完全に制御を握ることもできます。 標準のLuaでは64ビットの整数と倍精度 (64ビット) の浮動小数点数を使用します」

「3.4.2 – ビット演算子
ビット演算子には以下のものがあります。

&: ビットごとの論理積
|: ビットごとの論理和
~: ビットごとの排他的論理和
>>: 右シフト
<<: 左シフト
~: 単項ビットごとの否定
ビット演算子はすべて、引数を整数に変換し (§3.4.3 を参照)、その整数の全ビットを演算し、結果を整数で返します。

右シフトと左シフトは両方とも、空いたビットをゼロで埋めます。 変位に負の値を指定すると逆方向にシフトします。 変位の絶対値が整数のビット数以上であれば結果はゼロになります (すべてのビットが追い出されます)。」
困ったねぇ、、、つまり正直言ってLuaのバージョンを上げて欲しい!

Socket Debuggerを使ってみる! ポート:7!簡単なWeb Cliente

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

気を取り直して、Web Clienteとして動作させてみます。HTTP文を色々考えるのは面倒なので、Arduino IDEのEtherntのWeb Clienteの例題を流用してみます。と言ってもスクリプトは非常に簡単ですがね。
取得したHTTP文はファイルに保存します。ではでは、設定から。

webCliente_001.png今回はポート1を使います。通信設定->接続:TCPクライアント動作とします。相手先アドレスが判らなくても右横の...ってボタンをクリックすればhost名で入力できますから。


webCliente_002.png通信設定->動作:スクリプトで動かします。


webCliente_003.png受信したデータをファイル化し、ブラウザで表示してみました。HTML文の前にHTTPの応答文が入っています。


webCliente_004.pngWebサーバーとのやり取りです。Webサーバーはリクエストに答えると、基本切断して来ることが判ります。


今回作成したスクリプトです。作成したところのみを掲載しています。
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
  Logput(1,'OnSendPush')

  local httpRequest = 'GET /search?q=arduino HTTP/1.1\r\n'
    .. 'Host: www.google.com\r\n'
    .. 'Connection: close\r\n\r\n'  -- http request message.

  SendData( httpRequest )  -- send a request message to host.

  recvHtmlMsg = {}  -- make a table for recieve message.

  return 0
end
---------------------------------------------
-- 受信通知
---------------------------------------------
function OnReceive(recv)
  Logput(1,'OnReceive')

  recvHtmlMsg = tableConcat( recvHtmlMsg, recv )  -- append a message to variable

  return 0
end
---------------------------------------------
-- 切断通知
---------------------------------------------
function OnDisConnected()
  Logput(1,'OnDisConnected')

  FileWrite( recvHtmlMsg, "D:\\temp\\receive.html" )  -- file write

  return 0
end

---------------------------------------------
-- table concat
---------------------------------------------
function tableConcat( tableA, tableB )
  local tableALen = #tableA
  for i = 1, #tableB do
    tableA[ tableALen + i ] = tableB[i]
  end
  return tableA
end









Socket Debuggerを使ってみる! ポート:8!簡単なWeb Server

$
0
0
SocketDebugger

こことか、 https://www.udom.co.jp/sdg/
こことか、 http://sdg.ex-group.jp/lua.html

をまず見てね!

なにか意味が有るのかと思わなくもないが、沈みかかった船なのでWeb Serverとか

webServer_002.pngWeb ServerをSocket Debugger上に構築してブラウザからアクセスしてみた様子。内容はindex.htmlファイルとしてディスク上に保存、それを読み出す。


webServer_001.pngPCのブラウザとのやり取りの様子。


恒例のluaスクリプト
---------------------------------------------
-- 接続完了通知
---------------------------------------------
function OnConnected()
  Logput(1,'OnConnected')

  recvHttpTable = {}

  return 0
end
---------------------------------------------
-- 送信ボタン押下
---------------------------------------------
function OnSendPush()
    Logput(1,'OnSendPush')
    a = GetEditorData()
    SendData(a)
    return 0
end
---------------------------------------------
-- タイマー通知
---------------------------------------------
function OnTimer(id)
    Logput(1,'OnTimer')
    return 0
end
---------------------------------------------
-- 受信通知
-- 0x0D is CR,0x0A is LF
---------------------------------------------
function OnReceive(recv)
  Logput(1,'OnReceive')

  recvHttpTable = tableConcat( recvHttpTable, recv )
  if #recvHttpTable < 4 then return 0 end

  result = httpRequest( recvHttpTable )
  if result ~= 0 then
--[[
    if result == 200 then
    elseif result == 400 then
    elseif result == 404 then
    elseif result == 405 then
    else
    end
]]
    Disconnect()
  end

  return 0
end
---------------------------------------------
-- 切断通知
---------------------------------------------
function OnDisConnected()
  Logput(1,'OnDisConnected')
  Disconnect()
  return 0
end

---------------------------------------------
-- ASCIIテーブルをコード変換後に文字列化
--
-- ASCIIの例
-- 入力: { 0x30, 0x31, 0x32 }
-- 返値: "012"
--  http://amlaid.air-nifty.com/blog/2015/10/serial-debugger.html
-- 0x0D is CR,0x0A is LF
---------------------------------------------
function CharFromTbl(tbl)
  tblChar = {}

  for i=1, #tbl do
    if tbl[i] == 0x0D or 
       tbl[i] == 0x0A then
      tblChar[i] = string.char(tbl[i])
    else
      tblChar[i] = string.char(tbl[i])
    end
  end

  return table.concat(tblChar)
end


---------------------------------------------
-- table concat
---------------------------------------------
function tableConcat( tableA, tableB )
  local tableALen = #tableA

  for i = 1, #tableB do
    tableA[ tableALen + i ] = tableB[i]
  end

  return tableA
end


---------------------------------------------
-- http request
---------------------------------------------
function httpRequest( request )
  local len = #request
  local result = 0

  if request[len - 3] == 0x0D and
     request[len - 2] == 0x0A and 
     request[len - 1] == 0x0D and 
     request[len - 0] == 0x0A then
    Logput( 1, 'end of the request message.' )

    local line1 = getLine( request )

    local pos
    local method,url,httpVersion
    method,pos = split( line1, 0x20 )  -- 0x20 is ascii space
    line1 = tableCopyFrom( line1, pos + 1 )
    url,pos = split( line1, 0x20 )  -- 0x20 is ascii space
    httpVersion,pos = tableCopyFrom( line1, pos + 1 )

    local methodStr = CharFromTbl( method )
    if string.match( methodStr, 'GET' ) ~= nil then
      local data = methodGet( url,httpVersion )
      if data ~= nil then 
        index200( data )
        result = 200
      else
        index404()
        result = 404
      end
    elseif string.match( methodStr, 'PUT' ) ~= nil then
      index405()
      result = 405
    elseif string.match( methodStr, 'POST' ) ~= nil then
      index405()
      result = 405
    elseif string.match( methodStr, 'DELETE' ) ~= nil then
      index405()
      result = 405
    else
      index400()
      result = 400
    end
  end

  return result
end


---------------------------------------------
-- method get
---------------------------------------------
function methodGet( url, ver )
  local urlLen = #url
  local file = {}

  if urlLen == 1 then
    if url[1] == 0x2F then  -- 0x2F is /
      file = FileRead( "D:\\temp\\index.html" )
    else
      file = nil
    end
  else
    local urlStr = CharFromTbl( url )
    if string.match( urlStr, '/index.html' ) ~= nil or
       string.match( urlStr, '/index.htm' ) ~= nil then
      file = FileRead( "D:\\temp\\index.html" )
    else
      file = nil
    end
  end

  return file
end


---------------------------------------------
-- http request status code = 200
---------------------------------------------
function index200( data )
  local contentLength = #data
  local contentHeader = 'HTTP/1.1 200 OK\r\n'
    .. 'Server: SocketeEbugger Web Server/ ver.0.1\r\n'
    .. 'Content-Type: text/html; charset=UTF-8\r\n'
    .. 'Content-Length: '
    .. contentLength
    .. '\r\n'
    .. 'Connection: close\r\n\r\n'

  SendData( contentHeader )
  SendData( data )
end


---------------------------------------------
-- http request status code = 404
---------------------------------------------
function index404()
  local contentHeader = 'HTTP/1.0 404 Not Found\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>404 Not Found</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Not Found</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- http request status code = 400
---------------------------------------------
function index400()
  local contentHeader = 'HTTP/1.0 400 Bad Request\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>400 Bad Request</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Bad Request</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- http request status code = 405
---------------------------------------------
function index405()
  local contentHeader = 'HTTP/1.0 405 Method Not Allowed\r\n'
    .. 'Connection: close\r\n\r\n'
    .. '<html><head><title>405 Method Not Allowed</title></head>\r\n'
    .. '<body>\r\n'
    .. '<h1>Method Not Allowed</h1>\r\n'
    .. '</body></html>\r\n'

  SendData( contentHeader )
end


---------------------------------------------
-- get line from string
---------------------------------------------
function getLine( strTbl )
  local line = {}

  -- search line
  position = getDelimiterPosition( strTbl, 0x0D )  -- 0x0D is CR
  if position == 0 then return nil end

  position = position + 1
  for i = 1, position do
    line[i] = strTbl[i]
  end

  return line
end

---------------------------------------------
-- split table
---------------------------------------------
function split( tbl, delimiter )
  local index = 1
  local copyTbl = {}

  for i = 1, #tbl do
    if tbl[i] == delimiter then break end
    index = index + 1
    copyTbl[i] = tbl[i]
  end

  if index > #tbl then return nil,0 end

  return copyTbl,index
end

---------------------------------------------
-- get delimiter position
---------------------------------------------
function getDelimiterPosition( tbl, delimiter )
  local index = 1

  for i = 1, #tbl do
    if tbl[i] == delimiter then break end
    index = index + 1
  end

  if index > #tbl then return 0 end

  return index
end


---------------------------------------------
-- table copy from
---------------------------------------------
function tableCopyFrom( tbl, pos )
  local copyTbl = {}
  local index = 1

  for i = pos, #tbl do
    copyTbl[ index ] = tbl[ i ]
    index = index + 1
  end

  return copyTbl
end


PCのコントロールキーのパッドの中にちっさなゴミが入ったのか、気になって仕方がない、、、
※もう少し上手く書ける気がする。修行が足らん、、、








Viewing all 201 articles
Browse latest View live