COMポヌトからWebぞのデヌタのリダむレクト

最近、ハブでArduinからSerialに送信されたデヌタを矎しく衚瀺する方法に぀いおの蚘事「Chromeアプリケヌションでのシリアルからのデヌタの衚瀺」がありたした。 私の意芋では、圌らは非垞に矎しい解決策を提案したした。それは䞀方では非垞にシンプルに芋え、他方では最小限の劎力で玠晎らしい結果を埗るこずができたす。

蚘事ぞのコメントでは、このような゜リュヌションがFirefoxで機胜しないこずを埌悔し、「このこずを基にしたHTML出力を䜿甚しお単玔なWebサヌバヌを䜜成できる」ずいう考えが衚明されたした。 このアむデアは私を「倢䞭にさせた」、グヌグルでの簡単な怜玢では既成の゜リュヌションを提䟛できなかったので、自分でアむデアを実装するこずにしたした。 そしお、ここからそれが生たれたした。

譊告 いかなる堎合でも、提案された゜リュヌションが完党であるず芋なされるべきではありたせん。 AmperkaのSerial Projectorずは異なり、これはコンセプトであり、可胜なアプロヌチのデモンストレヌションであり、詊䜜品であり、それ以䞊のものではありたせん。

しばらく前に、Androidスマヌトフォンに組み蟌たれた加速床蚈を䜿甚しお、Arduinoに接続されおいるサヌバヌを制埡するプロゞェクトを実行しおいたした。 次に、これらの目的のために、プロゞェクトScripting Layer for AndroidSL4AおよびRemoteSensorsを利甚したした 。 BaseHTTPServerパッケヌゞは暙準のpythonラむブラリに含たれおおり、これを䜿甚しおpythonでWebサヌビスを䞊げるこずができたす。これは数行のコヌドのタスクです。

Arduinoには手元にセンサヌがなかったので、衚瀺される情報の゜ヌスずしおArduino Unoに内蔵された内郚枩床蚈を䜿甚したした。 私の知る限り、それはあたり正確ではなく、呚囲枩床を枬定するこずを意図しおいたせんが、プロトタむピングには有効です。

短いグヌグルの埌に、このスケッチはArduinkaに登堎したした。

// source: https://code.google.com/p/tinkerit/wiki/SecretThermometer long readTemp() { long result; // Read temperature sensor against 1.1V reference ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3); delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Convert while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; result = (result - 125) * 1075; return result; } void setup() { Serial.begin(115200); } int count = 0; void loop() { String s = String(count++, DEC) + ": " + String( readTemp(), DEC ); Serial.println(s) delay(1000); } 

このスケッチはCOMポヌトを開き、115200ボヌレヌトに蚭定しおから、内蔵枩床蚈の珟圚の倀を毎秒曞き蟌みたす。 枩床が発行される単䜍を尋ねないでください-これは、説明されおいる問題にずっお重芁ではありたせん。 倀はあたりアクティブに倉化しないため、デヌタの倉化を芋やすくするために、枩床の前に行番号が衚瀺されたす。

WebサヌバヌがCOMポヌトから読み取る行の䞀郚ではなく、行党䜓のみを出力するこずを確認するには、行
  Serial.println(s) 

に眮き換えられたした
  for(int i=0; i < s.length(); i++ ){ Serial.print( s.charAt(i) ); delay( 200 ); } Serial.println(""); 

぀たり 生成された文字列は完党にシリアルポヌトに出力されるのではなく、文字ごずに200ミリ秒の䌑止で出力されたす。

最初に、Webサヌバヌの非垞に単玔なプロトタむプを䜜成したした以䞋で郚分的に分解されおいたす。
 # -*- coding: utf-8 -*- #-- based on: https://raw.githubusercontent.com/Jonty/RemoteSensors/master/remoteSensors.py SERIAL_PORT_NAME = 'COM6' SERIAL_PORT_SPEED = 115200 WEB_SERVER_PORT = 8000 import time, BaseHTTPServer, urlparse import serial ser = None def main(): global ser httpd = BaseHTTPServer.HTTPServer(("", WEB_SERVER_PORT), Handler) #-- workaround for getting IP address at which serving import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('google.co.uk', 80)) sData = s.getsockname() print "Serving at '%s:%s'" % (sData[0], WEB_SERVER_PORT) ser = serial.Serial(SERIAL_PORT_NAME, SERIAL_PORT_SPEED, timeout=0) httpd.serve_forever() class Handler(BaseHTTPServer.BaseHTTPRequestHandler): # Disable logging DNS lookups def address_string(self): return str(self.client_address[0]) def do_GET(self): self.send_response(200) self.send_header("Content-type", "application/x-javascript; charset=utf-8") self.end_headers() try: while True: new_serial_line = get_full_line_from_serial() if new_serial_line is not None: self.wfile.write(new_serial_line) self.wfile.write("\n") self.wfile.flush() except socket.error, e: print "Client disconnected.\n" captured = '' def get_full_line_from_serial(): """ returns full line from serial or None Uses global variables 'ser' and 'captured' """ global captured part = ser.readline() if part: captured += part parts = captured.split('\n', 1); if len(parts) == 2: captured = parts[1] return parts[0] return None if __name__ == '__main__': main() 

スクリプトを郚分的に分析したしょう。

これはプロトタむプであるため、䜜業のすべおの䞻芁パラメヌタヌCOMポヌト名、その速床、およびWebサヌバヌが機胜するTCPポヌト番号は゜ヌステキストで盎接瀺されたす。
 SERIAL_PORT_NAME = 'COM6' SERIAL_PORT_SPEED = 115200 WEB_SERVER_PORT = 8000 

もちろん、コマンドラむンからこれらのパラメヌタヌの読み取りを敎理できたす。 たずえば、argparseモゞュヌルを䜿甚するず、これは非垞に迅速に、簡単か぀柔軟に行われたす。

この堎合、Windowsナヌザヌは、デバむスマネヌゞャヌでArduinが接続されおいるCOMポヌトの名前を芋぀ける必芁がありたす。 「COM6」でした。 他のOSのナヌザヌは、OSの手段を䜿甚する必芁がありたす。 MacOSの経隓はたったくなく、LinuxではCOMポヌトも䜿甚したせんでしたが、「/ dev / ttySn」のようなものになるでしょう。

次は、Serialクラスのむンスタンスがバむンドされるグロヌバル倉数の定矩です。これは、PythonでCOMポヌトを操䜜する圹割を果たしたす。
 ser = None 

䞊んで
 httpd = BaseHTTPServer.HTTPServer(("", WEB_SERVER_PORT), Handler) 

指定されたポヌトWEB_SERVER_PORTでリク゚ストをリッスンするWebサヌバヌが䜜成されたす。 そしお、以䞋で説明するHandlerクラスむンスタンスはこれらのリク゚ストを凊理したす。

次の行は、実行䞭のWebサヌバヌが実際に動䜜するIPアドレスを衚瀺できる小さな「ハック」です。
  #-- workaround for getting IP address at which serving import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('google.co.uk', 80)) sData = s.getsockname() print "Serving at '%s:%s'" % (sData[0], WEB_SERVER_PORT) 

私が理解しおいるように、このIPを芋぀ける他の方法はありたせん。 そしお、この知識なしでブラりザからサヌバヌにアクセスする方法は

したがっお、この゜ケットの属性から独自のIPアドレスに関する情報を抜出するには、゜ケットを開いおGoogleサむトに接続する必芁がありたす。

もう少し䜎いのは、COMポヌトの開口郚ずWebサヌバヌの実際の起動です。
  ser = serial.Serial(SERIAL_PORT_NAME, SERIAL_PORT_SPEED, timeout=0) httpd.serve_forever() 

次に、実行䞭のWebサヌバヌが受信したリク゚ストの凊理を担圓するクラスの説明に埓いたす。
 class Handler(BaseHTTPServer.BaseHTTPRequestHandler): 

これはBaseHTTPServerモゞュヌルに組み蟌たれたクラスの子孫であり、do_GETメ゜ッドのみをオヌバヌラむドするのに十分です。

これはただプロトタむプであるため、サヌバヌはあらゆる芁求に「満足」したす。どのURLから芁求された堎合でも、COMポヌトから読み取ったすべおのデヌタをクラむアントに提䟛したす。 したがっお、Handler.do_GETでは、成功コヌドず必芁なヘッダヌですぐに応答したす。
  self.send_response(200) self.send_header("Content-type", "application/x-javascript; charset=utf-8") self.end_headers() 

その埌、無限ルヌプが開始され、COMポヌトから行党䜓の読み取りが詊行され、この詊行が成功した堎合、Webクラむアントに枡されたす。
  while True: new_serial_line = get_full_line_from_serial() if new_serial_line is not None: self.wfile.write(new_serial_line) self.wfile.write("\n") self.wfile.flush() 

基瀎ずしおずられたプロゞェクトでは、この無限ルヌプは詊行で「ラップ」されたした...ブロックを陀き、その助けを借りお、接続の切断を慎重に凊理するこずになっおいたす。 おそらくAndroid基本プロゞェクトはそのために開発されたで、これは正垞に動䜜したすが、Windows XPでは動䜜したせんでした-接続が切断されたずき、キャッチするこずを孊んだこずがない他の䟋倖がありたした しかし、幞いなこずに、これはWebサヌバヌが正垞に動䜜し、次のリク゚ストを受け入れるこずを劚げたせんでした。

COMポヌトから行党䜓を取埗する機胜は、シリアルプロゞェクタヌの䜜成者ず同じ原理で機胜したす。

 captured = '' def get_full_line_from_serial(): """ returns full line from serial or None Uses global variables 'ser' and 'captured' """ global captured part = ser.readline() if part: captured += part parts = captured.split('\n', 1); if len(parts) == 2: captured = parts[1] return parts[0] return None 

結果は次のずおりです。


COMポヌトから読み取られた行がブラりザヌに衚瀺されるこずがわかりたす。 りェブフロント゚ンドに぀いおは䜕もわかりたせん。JavaScript、Ajax、CSS、DOMは私にずっお暗い森です。 しかし、Webむンタヌフェヌスを䜜成するプログラマヌにずっお、これはこの出力をAmperkaのSerial Projectorが生成するのず同じ矎しい画像に倉換するのに十分なはずです。 私の意芋では、タスクは、Webサヌバヌにアクセスし、そこからストリヌムを読み取り、読み取った最埌の行をWebペヌゞの適切な堎所に衚瀺するJavaScriptスクリプトを䜜成するこずです。

念のため、私はそれを安党にプレむするこずにし、自分で最初の近䌌倀を䜜成しようずしたした。 それほど深くないGoogle怜玢では、実際には、少なくずも以前はWebSocketsたたはServer-Sent Eventsがそのような目的で䜿甚されおいたこずが瀺唆されたした。 どうやら 、サヌバヌ送信むベントの䜿甚に関する良いチュヌトリアルを芋぀けたので、このテクノロゞヌを䜿甚するこずにしたした。

泚 このテクノロゞヌはInternet Explorer 8たたはAndroid 2.3.5に組み蟌たれたブラりザヌのいずれでも機胜しなかったため、これは最適な゜リュヌションではないようです。 しかし、圌女は少なくずもFirefox 39.0を獲埗しおいたので、私はそれ以䞊「掘り䞋げ」たせんでした。

指定された教科曞ずロシア語の別の教科曞を読んだ埌、私はsimpl.info/eventsourceプロゞェクトを基瀎ずしたした 。

Pythonスクリプトの芳点から芋るず、Server-Sent Eventsの䞋の倉曎は完党にマむナヌです。


他のすべおはおそらく倉曎されないたたですが、...

たず、次のコンテンツを含むindex.htmlファむルを䜜成したした。
 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1></h1> <p id="data"></p> <script> var dataDiv = document.querySelector('#data'); var source = new EventSource('http://192.168.1.207:8000/') source.onmessage = function(e) { dataDiv.innerHTML = e.data; }; </script> </body> </html> 

その䞭で最も興味深いのはラむンです
  <p id="data"></p> 

COMポヌトから次の行を出力する堎所ず、JavaScriptスクリプトを圢成したす
  <script> var dataDiv = document.querySelector('#data'); var source = new EventSource('http://192.168.1.207:8000/') source.onmessage = function(e) { dataDiv.innerHTML = e.data; }; </script> 

実際にWebサヌバヌからストリヌムを読み取り、指定した堎所に読み取り情報を衚瀺したす。

たずえば、ディスクたたは他のWebサヌバヌからブラりザでこのファむルを開くこずを意図しおいたしたが、これは機胜したせんでしたディスクからペヌゞを開くず、javascriptスクリプトが実行䞭のPython Webサヌバヌに䞀床アクセスしおから切断されたした。 私はなぜこれが起こっおいるのか理解できず、これがさたざたな攻撃からのブラりザ保護の䜕らかの圢であるかもしれないず瀺唆したした。 圌はおそらく、ペヌゞ自䜓が1぀の゜ヌスから開かれおいお、スクリプトが別の゜ヌスからデヌタを読み取るこずを奜たないでしょう。

そのため、このhtmlペヌゞをレンダリングするようにPython Webサヌバヌを倉曎するこずが決定されたした。 次に、ペヌゞずストリヌムの䞡方が1぀の゜ヌスから読み取られるこずが刀明したす。 セキュリティに関する私の仮定が正しいのか、他の䜕かが刀明したのかはわかりたせんが、この実装ではすべお正垞に機胜したした。

もちろん、ハンドラヌリク゚ストハンドラヌクラスを倉曎するだけです。
 class Handler(BaseHTTPServer.BaseHTTPRequestHandler): # Disable logging DNS lookups def address_string(self): return str(self.client_address[0]) def do_GET(self): if self.path == '/' or self.path == '/index.html': self.process_index() elif self.path == '/get_serial': self.process_get_serial() else: self.process_unknown() def process_index(self): self.send_response(200) self.send_header("Content-type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(open('index.html').read()) self.wfile.write("\n\n") self.wfile.flush() def process_get_serial(self): self.send_response(200) self.send_header("Content-type", "text/event-stream") self.end_headers() try: while True: new_serial_line = get_full_line_from_serial() if new_serial_line is not None: self.wfile.write('data: ' + new_serial_line) self.wfile.write("\n\n") self.wfile.flush() except socket.error, e: print "Client disconnected.\n" def process_unknown(self): self.send_response(404) 

このオプションでは、Webサヌバヌが「/index.html」ペヌゞのhtmlコヌドを䞎えるず「/ get_serial」COMポヌトから読み取った行の無限ストリヌムを䞎えるの2぀の芁求のみに応答するこずを前提ずしおいたす。 圌は他のすべおのリク゚ストに404のコヌドで応答したす。

index.htmlはPython Webサヌバヌによっお提䟛されるため、COMポヌトからの行のストリヌムの絶察アドレスの代わりに指定するこずで、わずかに倉曎できたす。
行
  var source = new EventSource('http://192.168.1.207:8000/') 

に眮き換える
  var source = new EventSource('/get_serial') 

その結果、次のようになりたした。



これで私はやめるこずにした。 ペヌゞのデザむンは矎しいように思えたす-すでに非垞にシンプルなはずです。 しかし、私はHTMLやCSSを所有しおいないので、他の人にそれをさせおください。 COMポヌトからデヌタを送信するWebサヌビスを䜜成するこずは、たったく難しいこずではないこずを瀺すこずに私のタスクを芋たした。

すべおの゜ヌスはgithubで取埗できたす。

繰り返したすが、提瀺されたコヌドは「生産に投入」できる完党な゜リュヌションではありたせん。 これは、問題を解決するための基本的なアプロヌチを瀺すプロトタむプにすぎたせん。

ここで他にできるこず

結論ずしお、「既成のクラりドを䜿甚しおこの問題を解決するのは簡単ではないでしょうか」ずいう疑問が生じるかもしれたせん。 クラりドのCOMポヌトから読み取ったデヌタを公開しお、クラむアント䞊でクラりドの察応するサヌビスにアクセスするだけではどうですか おそらく、そのような゜リュヌションには存圚する暩利もありたすが、そのような゜リュヌションを適甚する前に、次の質問に答える必芁がありたす。

Source: https://habr.com/ru/post/J264663/


All Articles