From 3132c50c3c3176307f2f905954d15633601fac64 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Fri, 16 Feb 2018 21:33:16 -0300 Subject: [PATCH 01/21] #6 Add websocket support for events with other devices --- .../request_message_processor.py | 19 +++++++- webservice_serial/webservice_serial.py | 25 +++++++++- webservice_serial/webservice_serial_client.py | 4 ++ webservice_serial/websocket_client.py | 48 +++++++++++++++++++ 4 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 webservice_serial/websocket_client.py diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index 05a2bd9..f4c5cef 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -10,10 +10,11 @@ class RequestMessageProcessor(object): :param port: Port that WebService are executing """ - def __init__(self, port): + def __init__(self, port, token=None): self.http_client = AsyncHTTPClient() self.url = 'http://localhost:{}'.format(port) self.processed_listener = lambda message, response: ... + self.token = token def process(self, message): """ @@ -22,12 +23,19 @@ def process(self, message): if message.verb is RequestVerb.SYSTEM: return - request = HTTPRequest(self.url + message.path, method=message.verb.value) + request = HTTPRequest(self.url + message.path, method=message.verb.value, headers=self.headers) self.http_client.fetch( request, lambda response: self.response(message, response=response) ) + @property + def headers(self): + if self.token is not None: + return {'x-xsrf-token': self.token} + else: + return None + def response(self, message, response): """ :param RequestMessage message: Request message @@ -53,3 +61,10 @@ def response(self, message, response): def close(self): self.http_client.close() + + def process_event(self, message): + """ + :param dict message: + :return ResponseMessage: + """ + return ResponseMessage(ResponseVerb.EVENT, str(message)) diff --git a/webservice_serial/webservice_serial.py b/webservice_serial/webservice_serial.py index ab6c79e..0574f06 100644 --- a/webservice_serial/webservice_serial.py +++ b/webservice_serial/webservice_serial.py @@ -1,7 +1,10 @@ +from tornado.ioloop import IOLoop + from application.component.component import Component -from webservice_serial.webservice_serial_client import WebServiceSerialClient from webservice_serial.request_message_processor import RequestMessageProcessor +from webservice_serial.webservice_serial_client import WebServiceSerialClient +from webservice_serial.websocket_client import WebSocketClient class WebServiceSerial(Component): @@ -18,6 +21,7 @@ def __init__(self, application, target, ws_port=3000): self.target = target self._client = WebServiceSerialClient('localhost', WebServiceSerial.port) self.request_message_processor = RequestMessageProcessor(ws_port) + self._websocket_client = WebSocketClient(ws_port) def init(self): self._client.connected_listener = self._on_connected @@ -26,11 +30,21 @@ def init(self): self.request_message_processor.processed_listener = self._on_processed self.target.init(self.application, WebServiceSerial.port) + + self._websocket_client.token_defined_listener = self._on_token_defined + self._websocket_client.message_listener = self._on_event + + self._websocket_client.connect() self._client.connect() + def _on_token_defined(self, token): + self.request_message_processor.token = token + def close(self): self.request_message_processor.close() self.target.close() + self._websocket_client.close() + self._client.close() def _on_connected(self): self.application.log('AndroidController - DisplayView connected') @@ -41,7 +55,14 @@ def _process_message(self, message): self.request_message_processor.process(message) def _on_processed(self, request_message, response_message): + response_message = self.target.process(request_message, response_message) + self.application.log('AndroidController - Message sent: {}', response_message) + self._client.send(response_message) - response_message = self.target.process(request_message, response_message) + def _on_event(self, message): + response_message = self.request_message_processor.process_event(message) + response_message = self.target.process(None, response_message) + + self.application.log('AndroidController - Message sent: {}', response_message) self._client.send(response_message) diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index d61aabf..6270beb 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -31,3 +31,7 @@ def connect(self): def send(self, message): text = str(message).encode(self.encoding) self.stream.write(text) + + def close(self): + if self.stream is not None: + self.stream.close() diff --git a/webservice_serial/websocket_client.py b/webservice_serial/websocket_client.py new file mode 100644 index 0000000..cb3788b --- /dev/null +++ b/webservice_serial/websocket_client.py @@ -0,0 +1,48 @@ +import json + +from tornado import gen +from tornado.ioloop import IOLoop +from tornado.websocket import websocket_connect + + +class WebSocketClient(object): + """ + :param int port: WebService port + """ + + def __init__(self, port): + self.port = port + self.connection = None + self.token_defined_listener = lambda token: ... + self.message_listener = lambda message: ... + + @property + def url(self): + return 'ws://localhost:{}/ws/'.format(self.port) + + @gen.coroutine + def connect(self): + IOLoop.current().spawn_callback(lambda: self._connect()) + + @gen.coroutine + def _connect(self): + self.connection = yield websocket_connect(self.url) + self._await_messages(self.connection) + + @gen.coroutine + def _await_messages(self, connection): + while True: + msg = yield connection.read_message() + if msg is None: + break + + message = json.loads(msg) + + if message['type'] == 'TOKEN': + self.token_defined_listener(message['value']) + else: + self.message_listener(message) + + def close(self): + if self.connection is not None: + self.connection.close() From 5e3d0b35f76d3c3269b0d025571ac7fc2a67b58b Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Fri, 16 Feb 2018 22:44:11 -0300 Subject: [PATCH 02/21] #6 Make TCP connection async --- webservice_serial/webservice_serial_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index 6270beb..5279961 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -1,4 +1,5 @@ from tornado import gen +from tornado.ioloop import IOLoop from tornado.tcpclient import TCPClient from webservice_serial.protocol.message_builder import MessageBuilder @@ -15,8 +16,11 @@ def __init__(self, address, port, encoding="utf-8"): self.message_listener = lambda message: ... self.connected_listener = lambda: ... - @gen.coroutine def connect(self): + IOLoop.current().spawn_callback(lambda: self._connect()) + + @gen.coroutine + def _connect(self): self.stream = yield TCPClient().connect(self.address, self.port) self.connected_listener() From 04b209da91c8aa2056f1f30f03509d7f142de69f Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Fri, 16 Feb 2018 22:47:35 -0300 Subject: [PATCH 03/21] FIX #10 - License Apache --- setup.py | 2 +- webservice_serial/__init__.py | 13 +++++++++++++ webservice_serial/protocol/__init__.py | 13 +++++++++++++ webservice_serial/protocol/message_builder.py | 14 ++++++++++++++ webservice_serial/protocol/request_message.py | 14 +++++++++++++- webservice_serial/protocol/request_verb.py | 14 ++++++++++++++ webservice_serial/protocol/response_message.py | 14 ++++++++++++++ webservice_serial/protocol/response_verb.py | 14 ++++++++++++++ webservice_serial/request_message_processor.py | 14 ++++++++++++++ webservice_serial/target/__init__.py | 13 +++++++++++++ webservice_serial/target/android/__init__.py | 13 +++++++++++++ webservice_serial/target/android/adb.py | 14 ++++++++++++++ .../target/android/android_display_view.py | 14 ++++++++++++++ webservice_serial/target/target.py | 15 +++++++++++++++ webservice_serial/webservice_serial.py | 14 +++++++++++++- webservice_serial/webservice_serial_client.py | 14 ++++++++++++++ webservice_serial/websocket_client.py | 14 ++++++++++++++ 17 files changed, 220 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 073b524..12020ad 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2017 SrMouraSilva +# Copyright 2018 SrMouraSilva # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/webservice_serial/__init__.py b/webservice_serial/__init__.py index e69de29..98a3ea2 100644 --- a/webservice_serial/__init__.py +++ b/webservice_serial/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/webservice_serial/protocol/__init__.py b/webservice_serial/protocol/__init__.py index e69de29..98a3ea2 100644 --- a/webservice_serial/protocol/__init__.py +++ b/webservice_serial/protocol/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/webservice_serial/protocol/message_builder.py b/webservice_serial/protocol/message_builder.py index 85e3820..be16d0c 100644 --- a/webservice_serial/protocol/message_builder.py +++ b/webservice_serial/protocol/message_builder.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import json from webservice_serial.protocol.request_message import RequestMessage diff --git a/webservice_serial/protocol/request_message.py b/webservice_serial/protocol/request_message.py index 534ff4d..acb5dfe 100644 --- a/webservice_serial/protocol/request_message.py +++ b/webservice_serial/protocol/request_message.py @@ -1,4 +1,16 @@ -from webservice_serial.protocol.request_verb import RequestVerb +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. class RequestMessage(object): diff --git a/webservice_serial/protocol/request_verb.py b/webservice_serial/protocol/request_verb.py index 6728006..5d19561 100644 --- a/webservice_serial/protocol/request_verb.py +++ b/webservice_serial/protocol/request_verb.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from enum import Enum diff --git a/webservice_serial/protocol/response_message.py b/webservice_serial/protocol/response_message.py index e64702f..9727b2b 100644 --- a/webservice_serial/protocol/response_message.py +++ b/webservice_serial/protocol/response_message.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import json diff --git a/webservice_serial/protocol/response_verb.py b/webservice_serial/protocol/response_verb.py index 203f828..f0e4bbc 100644 --- a/webservice_serial/protocol/response_verb.py +++ b/webservice_serial/protocol/response_verb.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from enum import Enum diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index f4c5cef..fd1ea77 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from tornado.httpclient import HTTPRequest, AsyncHTTPClient, HTTPError from webservice_serial.protocol.request_verb import RequestVerb diff --git a/webservice_serial/target/__init__.py b/webservice_serial/target/__init__.py index e69de29..98a3ea2 100644 --- a/webservice_serial/target/__init__.py +++ b/webservice_serial/target/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/webservice_serial/target/android/__init__.py b/webservice_serial/target/android/__init__.py index e69de29..98a3ea2 100644 --- a/webservice_serial/target/android/__init__.py +++ b/webservice_serial/target/android/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/webservice_serial/target/android/adb.py b/webservice_serial/target/android/adb.py index 6065d2b..e82a57e 100644 --- a/webservice_serial/target/android/adb.py +++ b/webservice_serial/target/android/adb.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import os diff --git a/webservice_serial/target/android/android_display_view.py b/webservice_serial/target/android/android_display_view.py index d0be418..a5be431 100644 --- a/webservice_serial/target/android/android_display_view.py +++ b/webservice_serial/target/android/android_display_view.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from webservice_serial.target.target import Target from webservice_serial.target.android.adb import Adb diff --git a/webservice_serial/target/target.py b/webservice_serial/target/target.py index b706ba3..6078a4c 100644 --- a/webservice_serial/target/target.py +++ b/webservice_serial/target/target.py @@ -1,3 +1,18 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + class Target(object): def __init__(self): self.application = None diff --git a/webservice_serial/webservice_serial.py b/webservice_serial/webservice_serial.py index 0574f06..029f755 100644 --- a/webservice_serial/webservice_serial.py +++ b/webservice_serial/webservice_serial.py @@ -1,4 +1,16 @@ -from tornado.ioloop import IOLoop +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from application.component.component import Component diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index 5279961..bbb5139 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from tornado import gen from tornado.ioloop import IOLoop from tornado.tcpclient import TCPClient diff --git a/webservice_serial/websocket_client.py b/webservice_serial/websocket_client.py index cb3788b..bc3a7ca 100644 --- a/webservice_serial/websocket_client.py +++ b/webservice_serial/websocket_client.py @@ -1,3 +1,17 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import json from tornado import gen From 262a06c2f65aaf063b5246fbd4555e9604a313d0 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Sat, 17 Feb 2018 10:59:23 -0300 Subject: [PATCH 04/21] #13 Try reconnect wme conectiom is closed --- CHANGES | 1 + webservice_serial/target/target.py | 4 ++ webservice_serial/webservice_serial.py | 23 ++++++++--- webservice_serial/webservice_serial_client.py | 38 +++++++++++++++++-- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index ef2a180..14369d9 100644 --- a/CHANGES +++ b/CHANGES @@ -3,3 +3,4 @@ Version 0.2.0 - released mm/dd/18 - Initial release - Change Android Controller -> WebServiceSerial: Now use WebService to reuse WebService methods - Defined Target: Initial target is Android, but is possible implements other targets for communication with other devices, as Arduino via USB Serial communication + - Try reconnect when connection is closed diff --git a/webservice_serial/target/target.py b/webservice_serial/target/target.py index 6078a4c..44cd1e2 100644 --- a/webservice_serial/target/target.py +++ b/webservice_serial/target/target.py @@ -18,6 +18,10 @@ def __init__(self): self.application = None self.port = None + @property + def name(self): + return self.__class__.__name__ + def init(self, application, port): """ Target initialization diff --git a/webservice_serial/webservice_serial.py b/webservice_serial/webservice_serial.py index 029f755..33504c5 100644 --- a/webservice_serial/webservice_serial.py +++ b/webservice_serial/webservice_serial.py @@ -18,6 +18,8 @@ from webservice_serial.webservice_serial_client import WebServiceSerialClient from webservice_serial.websocket_client import WebSocketClient +from time import sleep + class WebServiceSerial(Component): port = 8888 @@ -38,15 +40,21 @@ def __init__(self, application, target, ws_port=3000): def init(self): self._client.connected_listener = self._on_connected self._client.message_listener = self._process_message + self._client.disconnected_listener = lambda: self._try_connect(5) self.request_message_processor.processed_listener = self._on_processed - self.target.init(self.application, WebServiceSerial.port) - self._websocket_client.token_defined_listener = self._on_token_defined self._websocket_client.message_listener = self._on_event self._websocket_client.connect() + + self._try_connect() + + def _try_connect(self, delay=0): + self._log('Trying to connect with {}', self.target.name) + self.target.init(self.application, WebServiceSerial.port) + sleep(delay) self._client.connect() def _on_token_defined(self, token): @@ -59,22 +67,25 @@ def close(self): self._client.close() def _on_connected(self): - self.application.log('AndroidController - DisplayView connected') + self._log('{} connected', self.target.name) def _process_message(self, message): - self.application.log('AndroidController - Message received: {}', message) + self._log('Message received: {}', message) self.request_message_processor.process(message) def _on_processed(self, request_message, response_message): response_message = self.target.process(request_message, response_message) - self.application.log('AndroidController - Message sent: {}', response_message) + self._log('Message sent: {}', response_message) self._client.send(response_message) def _on_event(self, message): response_message = self.request_message_processor.process_event(message) response_message = self.target.process(None, response_message) - self.application.log('AndroidController - Message sent: {}', response_message) + self._log('Message sent: {}', response_message) self._client.send(response_message) + + def _log(self, message, *args, **kwargs): + self.application.log('{} - {}'.format(self.__class__.__name__, message), *args, **kwargs) diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index bbb5139..75d54d5 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -15,6 +15,7 @@ from tornado import gen from tornado.ioloop import IOLoop from tornado.tcpclient import TCPClient +from tornado.iostream import StreamClosedError from webservice_serial.protocol.message_builder import MessageBuilder @@ -30,26 +31,55 @@ def __init__(self, address, port, encoding="utf-8"): self.message_listener = lambda message: ... self.connected_listener = lambda: ... + self.disconnected_listener = lambda: print('Disconnected :(') + def connect(self): IOLoop.current().spawn_callback(lambda: self._connect()) @gen.coroutine def _connect(self): - self.stream = yield TCPClient().connect(self.address, self.port) + self.stream = yield self._try_connect() + if self.stream is None: + return + self.connected_listener() + yield self._start_read_data() + @gen.coroutine + def _try_connect(self): + try: + stream = yield TCPClient().connect(self.address, self.port) + return stream + except StreamClosedError as e: + self.disconnected_listener() + return None + + @gen.coroutine + def _start_read_data(self): while True: - data = yield self.stream.read_until('\n'.encode(self.encoding)) + data = yield self._read_data() + if data is None: + break data = data.decode(self.encoding).strip() generated = MessageBuilder.generate(data) if generated is not None: self.message_listener(generated) + @gen.coroutine + def _read_data(self): + try: + data = yield self.stream.read_until('\n'.encode(self.encoding)) + except StreamClosedError as e: + self.disconnected_listener() + return None + + return data + def send(self, message): text = str(message).encode(self.encoding) self.stream.write(text) def close(self): - if self.stream is not None: - self.stream.close() + if self.stream is not None and not self.stream.closed(): + self.stream.close() From c2a51147c3898810c64c422564bfb71e3a74d9ce Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Wed, 21 Feb 2018 19:26:46 -0300 Subject: [PATCH 05/21] #17 Keyboard support. Protocol improvement: Int Identifier --- README.rst | 77 ++++++++++++------- setup.py | 1 + .../protocol/keyboard/__init__.py | 13 ++++ .../protocol/keyboard/keyboard.py | 52 +++++++++++++ webservice_serial/protocol/message_builder.py | 9 ++- webservice_serial/protocol/request_message.py | 6 +- .../protocol/response_message.py | 14 ++-- webservice_serial/protocol/response_verb.py | 1 + .../request_message_processor.py | 12 +-- .../target/android/android_display_view.py | 2 +- webservice_serial/webservice_serial.py | 41 ++++++++++ webservice_serial/webservice_serial_client.py | 2 +- 12 files changed, 182 insertions(+), 48 deletions(-) create mode 100644 webservice_serial/protocol/keyboard/__init__.py create mode 100644 webservice_serial/protocol/keyboard/keyboard.py diff --git a/README.rst b/README.rst index 3de075f..c70c6e8 100644 --- a/README.rst +++ b/README.rst @@ -25,33 +25,14 @@ With it, is possible: .. _Apache License 2.0: https://github.com/PedalPi/WebServiceSerial/blob/master/LICENSE -~How to use~ FIXME ------------------- +Installation +------------ -Like described in `Application documentation`_, create a ``start.py`` -and register AndroidController component. - -.. code:: python +Install with pip: - import sys - import tornado - - # DEPRECATED - sys.path.append('application') - sys.path.append('android_controller') +.. code-block:: bash - from application.Application import Application - from android_controller.android_controller import AndroidController - - address = 'localhost' - port = 3000 - - application = Application(path_data="data/", address=address, test=True) - application.register(AndroidController(application, "adb")) - - application.start() - - tornado.ioloop.IOLoop.current().start() + pip install PedalPi-PluginsManager Dependencies ~~~~~~~~~~~~ @@ -65,6 +46,42 @@ If you uses in a ARM architecture, maybe will be necessary compile can help you. **adb-arm** PedalPi *fork* already contains some binaries for RaspberryPi. +How to use +---------- + +Like described in `Application documentation`_, create a ``start.py`` +and register AndroidController component. + +.. code:: python + + # Imports application + from application.application import Application + + address = 'localhost' + application = Application(path_data="data/", address=address) + + # Register WebService before WebServiceSerial + from webservice.webservice import WebService + application.register(WebService(application)) + + # Register WebServiceSerial after WebService + from webservice_serial.webservice_serial import WebServiceSerial + from webservice_serial.target.android.android_display_view import AndroidDisplayView + + target = AndroidDisplayView() + application.register(WebServiceSerial(application, target)) + + # Start Application + application.start() + + import tornado + try: + tornado.ioloop.IOLoop.current().start() + except KeyboardInterrupt: + application.stop() + + + Protocol -------- @@ -76,10 +93,12 @@ Request :: - \n + \n \n EOF\n +- ````: ``int`` that informs the request + Responses will have the same identifier; - ````: ``GET``, ``POST``, ``PUT``, ``DELETE``, ``SYSTEM`` - ````: Json data. If none, send ``'{}'`` - ````: http://pedalpi.github.io/WebService/ @@ -89,7 +108,7 @@ Example: :: - PUT /current/bank/1/pedalboard/3 + 1 PUT /current/bank/1/pedalboard/3 {} EOF @@ -98,8 +117,10 @@ Response :: - RESPONSE + RESPONSE +- ````: ``int``. A response returns the same ``int`` that the request + informs; - ``RESPONSE``: String ``RESPONSES``; - ````: Json data. If none, send ``'{}'`` @@ -110,7 +131,7 @@ This corresponds the websocket data notifications :: - EVENT + EVENT - ``EVENT``: String ``EVENT`` - ````: Json data. If none, send ``'{}'`` diff --git a/setup.py b/setup.py index 12020ad..d1e284b 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ def readme(): packages=[ 'webservice_serial', 'webservice_serial/protocol', + 'webservice_serial/protocol/keyboard', 'webservice_serial/target', 'webservice_serial/target/android', ], diff --git a/webservice_serial/protocol/keyboard/__init__.py b/webservice_serial/protocol/keyboard/__init__.py new file mode 100644 index 0000000..98a3ea2 --- /dev/null +++ b/webservice_serial/protocol/keyboard/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/webservice_serial/protocol/keyboard/keyboard.py b/webservice_serial/protocol/keyboard/keyboard.py new file mode 100644 index 0000000..b42a690 --- /dev/null +++ b/webservice_serial/protocol/keyboard/keyboard.py @@ -0,0 +1,52 @@ +# Copyright 2018 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import Enum + + +class KeyCode(Enum): + DOWN = "DOWN" + UP = "EVENT" + + +class KeyNumber(Enum): + DPAD_UP = 0x00000013 + DPAD_DOWN = 0x00000014 + DPAD_LEFT = 0x00000015 + DPAD_RIGHT = 0x00000016 + + DPAD_CENTER = 0x00000017 + + PLUS = 0x00000051 + MINUS = 0x00000045 + + +class KeyEvent(object): + """ + :param KeyCode code: + :param KeyNumber number: + """ + + def __init__(self, code, number): + self.code = code + self.number = number + + def __dict__(self): + return { + 'code': self.code.value, + 'number': self.number.value + } + + def __str__(self): + return str(self.__dict__()) diff --git a/webservice_serial/protocol/message_builder.py b/webservice_serial/protocol/message_builder.py index be16d0c..f2ea08a 100644 --- a/webservice_serial/protocol/message_builder.py +++ b/webservice_serial/protocol/message_builder.py @@ -33,12 +33,12 @@ def generate(message): buffer = MessageBuilder.clean_buffer() - verb, path = buffer[0].split(" ") + identifier, verb, path = buffer[0].split(" ") data = buffer[1] verb = MessageBuilder.discover_verb(verb) - return MessageBuilder.generate_request_message(verb, path, data) + return MessageBuilder.generate_request_message(identifier, verb, path, data) @staticmethod def clean_buffer(): @@ -55,11 +55,12 @@ def discover_verb(word): return RequestVerb.SYSTEM @staticmethod - def generate_request_message(verb, path, data): + def generate_request_message(identifier, verb, path, data): """ + :param int identifier: Sequence number :param RequestVerb verb: Verb :param string path: Path :param string data: Data :return RequestMessage: message generated """ - return RequestMessage(verb, path, json.loads(data)) + return RequestMessage(identifier, verb, path, json.loads(data)) diff --git a/webservice_serial/protocol/request_message.py b/webservice_serial/protocol/request_message.py index acb5dfe..0f0a123 100644 --- a/webservice_serial/protocol/request_message.py +++ b/webservice_serial/protocol/request_message.py @@ -17,15 +17,17 @@ class RequestMessage(object): """ Message send form the app to the AndroidController + :param int identifier: Sequence number :param RequestVerb verb: :param string path: :param dict content: """ - def __init__(self, verb, path, content): + def __init__(self, identifier, verb, path, content): + self.identifier = identifier self.verb = verb self.path = path self.content = content def __str__(self): - return '{} {}\n{}\nEOF'.format(self.verb, self.path, self.content) + return '{} {} {}\n{}\nEOF'.format(self.identifier, self.verb, self.path, self.content) diff --git a/webservice_serial/protocol/response_message.py b/webservice_serial/protocol/response_message.py index 9727b2b..8a63072 100644 --- a/webservice_serial/protocol/response_message.py +++ b/webservice_serial/protocol/response_message.py @@ -16,14 +16,16 @@ class ResponseMessage(object): + """ + :param ResponseVerb verb: + :param object content: + :param int identifier: + """ - def __init__(self, verb, content=None): - """ - :param ResponseVerb verb: - :param string content: - """ + def __init__(self, verb, content=None, identifier=0): + self.identifier = identifier self.verb = verb self.content = json.dumps({}) if content is None else content def __str__(self): - return "{} {}\n".format(self.verb, self.content) + return "{} {} {}\n".format(self.identifier, self.verb, str(self.content)) diff --git a/webservice_serial/protocol/response_verb.py b/webservice_serial/protocol/response_verb.py index f0e4bbc..3687c5c 100644 --- a/webservice_serial/protocol/response_verb.py +++ b/webservice_serial/protocol/response_verb.py @@ -18,6 +18,7 @@ class ResponseVerb(Enum): RESPONSE = "RESPONSE" EVENT = "EVENT" + KEYBOARD_EVENT = "KEYBOARD_EVENT" def __str__(self): return self.value diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index fd1ea77..514f525 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -50,14 +50,14 @@ def headers(self): else: return None - def response(self, message, response): + def response(self, request, http_response): """ - :param RequestMessage message: Request message - :param HTTPResponse response: Response message + :param RequestMessage request: Request message + :param HTTPResponse http_response: WebService response message :return: """ try: - response_message = ResponseMessage(ResponseVerb.RESPONSE, response.body.decode('utf8')) + response = ResponseMessage(ResponseVerb.RESPONSE, http_response.body.decode('utf8'), identifier=request.identifier) except HTTPError as e: # HTTPError is raised for non-200 responses; the response @@ -71,7 +71,7 @@ def response(self, message, response): print("Error: " + str(e)) return - self.processed_listener(message, response_message) + self.processed_listener(request, response) def close(self): self.http_client.close() @@ -81,4 +81,4 @@ def process_event(self, message): :param dict message: :return ResponseMessage: """ - return ResponseMessage(ResponseVerb.EVENT, str(message)) + return ResponseMessage(ResponseVerb.EVENT, message) diff --git a/webservice_serial/target/android/android_display_view.py b/webservice_serial/target/android/android_display_view.py index a5be431..ebd70b5 100644 --- a/webservice_serial/target/android/android_display_view.py +++ b/webservice_serial/target/android/android_display_view.py @@ -21,7 +21,7 @@ class AndroidDisplayView(Target): :param string adb_command: Command that call the Android Debug Bridge In Raspberry maybe be a `./adb` executable file """ - activity = 'io.github.pedalpi.pedalpi_display/io.github.pedalpi.pedalpi_display.MainActivity' + activity = 'io.github.pedalpi.pedalpi_display/io.github.pedalpi.displayview.MainActivity' def __init__(self, adb_command="adb"): super(AndroidDisplayView, self).__init__() diff --git a/webservice_serial/webservice_serial.py b/webservice_serial/webservice_serial.py index 33504c5..cb5d09c 100644 --- a/webservice_serial/webservice_serial.py +++ b/webservice_serial/webservice_serial.py @@ -13,6 +13,7 @@ # limitations under the License. from application.component.component import Component +from webservice_serial.protocol.response_verb import ResponseVerb from webservice_serial.request_message_processor import RequestMessageProcessor from webservice_serial.webservice_serial_client import WebServiceSerialClient @@ -69,7 +70,47 @@ def close(self): def _on_connected(self): self._log('{} connected', self.target.name) + from webservice_serial.protocol.keyboard.keyboard import KeyEvent, KeyNumber, KeyCode + from webservice_serial.protocol.response_message import ResponseMessage + from webservice_serial.protocol.response_verb import ResponseVerb + + from time import sleep + while True: + # sleep(1) + # self.target.adb.execute('shell input keyevent 19') + # sleep(1) + # self.target.adb.execute('shell input keyevent 20') + message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_DOWN) + msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) + self._log('Message sent: {}', msg) + self._client.send(msg) + + message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_DOWN) + msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) + self._log('Message sent: {}', msg) + self._client.send(msg) + + sleep(1) + + message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_UP) + msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) + self._log('Message sent: {}', msg) + self._client.send(msg) + + ''' + sleep(1) + + message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_CENTER) + msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) + self._log('Message sent: {}', msg) + self._client.send(msg) + ''' + break + def _process_message(self, message): + """ + :param RequestMessage message: + """ self._log('Message received: {}', message) self.request_message_processor.process(message) diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index 75d54d5..3866758 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -82,4 +82,4 @@ def send(self, message): def close(self): if self.stream is not None and not self.stream.closed(): - self.stream.close() + self.stream.close() From 804c0af5433211615e455e67efb169e7f8f7a52e Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 22 Feb 2018 11:41:09 -0300 Subject: [PATCH 06/21] #13 Try reconnect when the stream closes when are sending data. #17 fix call method --- webservice_serial/request_message_processor.py | 2 +- webservice_serial/webservice_serial_client.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index 514f525..7d0d77b 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -40,7 +40,7 @@ def process(self, message): request = HTTPRequest(self.url + message.path, method=message.verb.value, headers=self.headers) self.http_client.fetch( request, - lambda response: self.response(message, response=response) + lambda http_response: self.response(message, http_response) ) @property diff --git a/webservice_serial/webservice_serial_client.py b/webservice_serial/webservice_serial_client.py index 3866758..0af223b 100644 --- a/webservice_serial/webservice_serial_client.py +++ b/webservice_serial/webservice_serial_client.py @@ -77,8 +77,11 @@ def _read_data(self): return data def send(self, message): - text = str(message).encode(self.encoding) - self.stream.write(text) + try: + text = str(message).encode(self.encoding) + self.stream.write(text) + except StreamClosedError as e: + self.disconnected_listener() def close(self): if self.stream is not None and not self.stream.closed(): From 87ec1e65ce85cd9c4d4699215590d87fd79882d4 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Fri, 23 Feb 2018 23:29:01 -0300 Subject: [PATCH 07/21] Fix bugs request protocol -> webservice request --- webservice_serial/protocol/message_builder.py | 6 ++++- webservice_serial/protocol/request_message.py | 8 ++++++- .../protocol/response_message.py | 5 ++++ webservice_serial/protocol/response_verb.py | 2 ++ .../request_message_processor.py | 24 +++++++------------ 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/webservice_serial/protocol/message_builder.py b/webservice_serial/protocol/message_builder.py index f2ea08a..edc81c4 100644 --- a/webservice_serial/protocol/message_builder.py +++ b/webservice_serial/protocol/message_builder.py @@ -63,4 +63,8 @@ def generate_request_message(identifier, verb, path, data): :param string data: Data :return RequestMessage: message generated """ - return RequestMessage(identifier, verb, path, json.loads(data)) + data = json.loads(data) + if verb == RequestVerb.GET: + data = None + + return RequestMessage(identifier, verb, path, data) diff --git a/webservice_serial/protocol/request_message.py b/webservice_serial/protocol/request_message.py index 0f0a123..cab1718 100644 --- a/webservice_serial/protocol/request_message.py +++ b/webservice_serial/protocol/request_message.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json + class RequestMessage(object): """ @@ -23,11 +25,15 @@ class RequestMessage(object): :param dict content: """ - def __init__(self, identifier, verb, path, content): + def __init__(self, identifier, verb, path, content=None): self.identifier = identifier self.verb = verb self.path = path self.content = content + @property + def content_formatted(self): + return json.dumps(self.content) if self.content is not None else None + def __str__(self): return '{} {} {}\n{}\nEOF'.format(self.identifier, self.verb, self.path, self.content) diff --git a/webservice_serial/protocol/response_message.py b/webservice_serial/protocol/response_message.py index 8a63072..9263c5c 100644 --- a/webservice_serial/protocol/response_message.py +++ b/webservice_serial/protocol/response_message.py @@ -13,6 +13,7 @@ # limitations under the License. import json +from webservice_serial.protocol.response_verb import ResponseVerb class ResponseMessage(object): @@ -22,6 +23,10 @@ class ResponseMessage(object): :param int identifier: """ + @staticmethod + def error(message, identifier=0): + return ResponseMessage(ResponseVerb.ERROR, '{"message": "'+message+'"}', identifier=identifier) + def __init__(self, verb, content=None, identifier=0): self.identifier = identifier self.verb = verb diff --git a/webservice_serial/protocol/response_verb.py b/webservice_serial/protocol/response_verb.py index 3687c5c..5769547 100644 --- a/webservice_serial/protocol/response_verb.py +++ b/webservice_serial/protocol/response_verb.py @@ -20,5 +20,7 @@ class ResponseVerb(Enum): EVENT = "EVENT" KEYBOARD_EVENT = "KEYBOARD_EVENT" + ERROR = "ERROR" + def __str__(self): return self.value diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index 7d0d77b..58bb476 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -17,7 +17,7 @@ from webservice_serial.protocol.request_verb import RequestVerb from webservice_serial.protocol.response_message import ResponseMessage from webservice_serial.protocol.response_verb import ResponseVerb - +import json class RequestMessageProcessor(object): """ @@ -37,7 +37,8 @@ def process(self, message): if message.verb is RequestVerb.SYSTEM: return - request = HTTPRequest(self.url + message.path, method=message.verb.value, headers=self.headers) + request = HTTPRequest(self.url + message.path, method=message.verb.value, headers=self.headers, + body=message.content_formatted) self.http_client.fetch( request, lambda http_response: self.response(message, http_response) @@ -56,20 +57,11 @@ def response(self, request, http_response): :param HTTPResponse http_response: WebService response message :return: """ - try: - response = ResponseMessage(ResponseVerb.RESPONSE, http_response.body.decode('utf8'), identifier=request.identifier) - - except HTTPError as e: - # HTTPError is raised for non-200 responses; the response - # can be found in e.response. - print("Error: " + str(e)) - #FIXME - return - - except Exception as e: - # Other errors are possible, such as IOError. - print("Error: " + str(e)) - return + if http_response.code == 405: + response = ResponseMessage.error(http_response.body.decode('utf8'), request.identifier) + else: + response = ResponseMessage(ResponseVerb.RESPONSE, http_response.body.decode('utf8'), + identifier=request.identifier) self.processed_listener(request, response) From 7e7878d8553653243c6b9cc3635aa36050a459e5 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Sat, 24 Feb 2018 18:24:25 -0300 Subject: [PATCH 08/21] Fix request bugs --- webservice_serial/protocol/message_builder.py | 17 +-------- webservice_serial/protocol/request_message.py | 6 +-- .../request_message_processor.py | 11 +++--- webservice_serial/webservice_serial.py | 37 ------------------- 4 files changed, 10 insertions(+), 61 deletions(-) diff --git a/webservice_serial/protocol/message_builder.py b/webservice_serial/protocol/message_builder.py index edc81c4..abd5d83 100644 --- a/webservice_serial/protocol/message_builder.py +++ b/webservice_serial/protocol/message_builder.py @@ -38,7 +38,7 @@ def generate(message): verb = MessageBuilder.discover_verb(verb) - return MessageBuilder.generate_request_message(identifier, verb, path, data) + return RequestMessage(identifier, verb, path, data) @staticmethod def clean_buffer(): @@ -53,18 +53,3 @@ def discover_verb(word): return verb return RequestVerb.SYSTEM - - @staticmethod - def generate_request_message(identifier, verb, path, data): - """ - :param int identifier: Sequence number - :param RequestVerb verb: Verb - :param string path: Path - :param string data: Data - :return RequestMessage: message generated - """ - data = json.loads(data) - if verb == RequestVerb.GET: - data = None - - return RequestMessage(identifier, verb, path, data) diff --git a/webservice_serial/protocol/request_message.py b/webservice_serial/protocol/request_message.py index cab1718..00d4bec 100644 --- a/webservice_serial/protocol/request_message.py +++ b/webservice_serial/protocol/request_message.py @@ -22,10 +22,10 @@ class RequestMessage(object): :param int identifier: Sequence number :param RequestVerb verb: :param string path: - :param dict content: + :param string content: """ - def __init__(self, identifier, verb, path, content=None): + def __init__(self, identifier, verb, path, content): self.identifier = identifier self.verb = verb self.path = path @@ -33,7 +33,7 @@ def __init__(self, identifier, verb, path, content=None): @property def content_formatted(self): - return json.dumps(self.content) if self.content is not None else None + return self.content if self.content != "" else None def __str__(self): return '{} {} {}\n{}\nEOF'.format(self.identifier, self.verb, self.path, self.content) diff --git a/webservice_serial/request_message_processor.py b/webservice_serial/request_message_processor.py index 58bb476..52fcdea 100644 --- a/webservice_serial/request_message_processor.py +++ b/webservice_serial/request_message_processor.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tornado.httpclient import HTTPRequest, AsyncHTTPClient, HTTPError +from tornado.httpclient import HTTPRequest, AsyncHTTPClient from webservice_serial.protocol.request_verb import RequestVerb from webservice_serial.protocol.response_message import ResponseMessage from webservice_serial.protocol.response_verb import ResponseVerb -import json + class RequestMessageProcessor(object): """ @@ -57,11 +57,12 @@ def response(self, request, http_response): :param HTTPResponse http_response: WebService response message :return: """ + body = http_response.body.decode('utf8') if http_response.body is not None else None + if http_response.code == 405: - response = ResponseMessage.error(http_response.body.decode('utf8'), request.identifier) + response = ResponseMessage.error(body, request.identifier) else: - response = ResponseMessage(ResponseVerb.RESPONSE, http_response.body.decode('utf8'), - identifier=request.identifier) + response = ResponseMessage(ResponseVerb.RESPONSE, body, identifier=request.identifier) self.processed_listener(request, response) diff --git a/webservice_serial/webservice_serial.py b/webservice_serial/webservice_serial.py index cb5d09c..4562c5c 100644 --- a/webservice_serial/webservice_serial.py +++ b/webservice_serial/webservice_serial.py @@ -70,43 +70,6 @@ def close(self): def _on_connected(self): self._log('{} connected', self.target.name) - from webservice_serial.protocol.keyboard.keyboard import KeyEvent, KeyNumber, KeyCode - from webservice_serial.protocol.response_message import ResponseMessage - from webservice_serial.protocol.response_verb import ResponseVerb - - from time import sleep - while True: - # sleep(1) - # self.target.adb.execute('shell input keyevent 19') - # sleep(1) - # self.target.adb.execute('shell input keyevent 20') - message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_DOWN) - msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) - self._log('Message sent: {}', msg) - self._client.send(msg) - - message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_DOWN) - msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) - self._log('Message sent: {}', msg) - self._client.send(msg) - - sleep(1) - - message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_UP) - msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) - self._log('Message sent: {}', msg) - self._client.send(msg) - - ''' - sleep(1) - - message = KeyEvent(KeyCode.DOWN, KeyNumber.DPAD_CENTER) - msg = ResponseMessage(ResponseVerb.KEYBOARD_EVENT, message) - self._log('Message sent: {}', msg) - self._client.send(msg) - ''' - break - def _process_message(self, message): """ :param RequestMessage message: From 678a8d02b05a7a0cc93b8fa9a7e86bb7bbaec686 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Tue, 6 Mar 2018 20:04:20 -0300 Subject: [PATCH 09/21] #19 Initial tests with USB detection (#20) and android usb acessory --- webservice_serial/aoa/__init__.py | 0 webservice_serial/aoa/pyaoa.py | 18 +++++++++ webservice_serial/aoa/usb_devices.py | 53 ++++++++++++++++++++++++++ webservice_serial/aoa/usv_events.py | 56 ++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 webservice_serial/aoa/__init__.py create mode 100644 webservice_serial/aoa/pyaoa.py create mode 100644 webservice_serial/aoa/usb_devices.py create mode 100644 webservice_serial/aoa/usv_events.py diff --git a/webservice_serial/aoa/__init__.py b/webservice_serial/aoa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webservice_serial/aoa/pyaoa.py b/webservice_serial/aoa/pyaoa.py new file mode 100644 index 0000000..f2732a4 --- /dev/null +++ b/webservice_serial/aoa/pyaoa.py @@ -0,0 +1,18 @@ +from core import * + +dev = find_device([(0xfce, 0x518c)]) +#toggle_accessory_mode(dev, "PedalPi", "Display-View", "Description", "1.0", "http://www.github.com/PedalPi/DisplayView", "SN-123-456-789") +#toggle_accessory_mode(dev, "Manufacturer", "Model", "Description", "1.0", "http://www.github.com/PedalPi/DisplayView", "SN-123-456-789") + +MANUFACTURER = "Pedal Pi" +MODEL_NAME = "Display View" +DESCRIPTION = "Pedal Pi - Display View" +VERSION = "0.3.0" +URL = "http://github.com/PedalPi/DisplayView" +SERIAL_NUMBER = "0001" + +toggle_accessory_mode(dev, MANUFACTURER, MODEL_NAME, DESCRIPTION, VERSION, URL, SERIAL_NUMBER) +dev = find_accessory() + +import time +time.sleep(100) \ No newline at end of file diff --git a/webservice_serial/aoa/usb_devices.py b/webservice_serial/aoa/usb_devices.py new file mode 100644 index 0000000..e429012 --- /dev/null +++ b/webservice_serial/aoa/usb_devices.py @@ -0,0 +1,53 @@ +from pyudev import Context, Monitor, MonitorObserver + +context = Context() +''' +monitor = Monitor.from_netlink(context) +monitor.filter_by(subsystem='input') +def print_device_event(device): + print('background event {0.action}: {0.device_path}'.format(device)) +observer = MonitorObserver(monitor, callback=print_device_event, name='monitor-observer') +print(observer.daemon) +observer.start() + +from pyudev import Context, Monitor +context = Context() +monitor = Monitor.from_netlink(context) +monitor.filter_by('input') +device = monitor.poll(timeout=1) +if device: + print('{0.action}: {0}'.format(device)) +''' +import time + +#while True: +# time.sleep(1) + +''' +import pyudev +context = pyudev.Context() +monitor = Monitor.from_netlink(context) +# For USB devices +monitor.filter_by(subsystem='usb') +# OR specifically for most USB serial devices +#monitor.filter_by(subsystem='tty') +for action, device in monitor: + vendor_id = device.get('ID_VENDOR_ID') + # I know the devices I am looking for have a vendor ID of '22fa' + print(vendor_id) + #if vendor_id in ['22fa']: + # print('Detected {} for device with vendor ID {}'.format(action, vendor_id)) +''' + +import usb.core +# find USB devices +dev = usb.core.find(find_all=True) +# loop through devices, printing vendor and product ids in decimal and hex +for cfg in dev: + try: + print(cfg.manufacturer) + except Exception: + print("error") + print('Decimal VendorID=' + str(cfg.idVendor) + ' & ProductID=' + str(cfg.idProduct)) + print('Hexadecimal VendorID=' + hex(cfg.idVendor) + ' & ProductID=' + hex(cfg.idProduct)) + print() \ No newline at end of file diff --git a/webservice_serial/aoa/usv_events.py b/webservice_serial/aoa/usv_events.py new file mode 100644 index 0000000..8663768 --- /dev/null +++ b/webservice_serial/aoa/usv_events.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# accessory.py +# License GPLv2 +# (c) Manuel Di Cerbo, Nexus-Computing GmbH +# https://github.com/rosterloh/pyusb-android/blob/master/accessory.py +import os +import socket + + +NETLINK_KOBJECT_UEVENT = 15 + + +def main(): + while True: + sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_KOBJECT_UEVENT) + sock.bind((os.getpid(), -1)) + + while True: + data = sock.recv(512) + vid = parse_uevent(data) + if vid is not None: + break + + sock.close() + accessory_task(vid) + + +def parse_uevent(data): + """ + :param bytes data: + :return: + """ + + lines = data.decode('UTF8', 'ignore').split('\0') + + #lines = data.decode('UTF8').split('\0')#.replace('\fe', b'').decode('UTF8') + print(lines) + keys = [] + for line in lines: + val = line.split('=') + if len(val) == 2: + keys.append((val[0], val[1])) + + attributes = dict(keys) + if 'ACTION' in attributes and 'PRODUCT' in attributes: + if attributes['ACTION'] == 'add': + parts = attributes['PRODUCT'].split('/') + return int(parts[0], 16) + + return None + +def accessory_task(teste): + print(teste) + +if __name__ == '__main__': + main() \ No newline at end of file From f9f7e079c77548de2ca24695a2617510c4cd10a3 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Tue, 6 Mar 2018 21:34:00 -0300 Subject: [PATCH 10/21] #21 Initial changes --- README.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index c70c6e8..3a1bd41 100644 --- a/README.rst +++ b/README.rst @@ -6,10 +6,9 @@ Serial communication. With it, is possible: -- Use `DisplayView`_, an Android application that provides pedalboard - data for live presentations. Your focus is a speed management in live - performances. - +- Use `DisplayView`_, an Android application for manages quickly the + current pedalboard. Ideal for adjusting live performances and band + rehearsal. **Documentation:** https://github.com/PedalPi/WebServiceSerial @@ -109,7 +108,7 @@ Example: :: 1 PUT /current/bank/1/pedalboard/3 - {} + EOF Response @@ -122,7 +121,7 @@ Response - ````: ``int``. A response returns the same ``int`` that the request informs; - ``RESPONSE``: String ``RESPONSES``; -- ````: Json data. If none, send ``'{}'`` +- ````: Json data. If none, send ``''`` Notification ~~~~~~~~~~~~ @@ -134,7 +133,7 @@ This corresponds the websocket data notifications EVENT - ``EVENT``: String ``EVENT`` -- ````: Json data. If none, send ``'{}'`` +- ````: Json data. If none, send ``''`` Initialization ~~~~~~~~~~~~~~ @@ -159,7 +158,7 @@ The connected device can be request thinks, like: :: GET /v1/current - {} + EOF - Response From dd0c106e0d67ff7582e999111bb2a4772b80ff2f Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Wed, 7 Mar 2018 20:53:41 -0300 Subject: [PATCH 11/21] #21 Improve Readme --- README.rst | 147 +++++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/README.rst b/README.rst index 3a1bd41..d35378e 100644 --- a/README.rst +++ b/README.rst @@ -33,23 +33,27 @@ Install with pip: pip install PedalPi-PluginsManager -Dependencies -~~~~~~~~~~~~ +Also is necessary install the `Android Debug Bridge (adb)`_ +for communication between the Pedal Pi and a Android device. In a Linux like, execute -**WebService Serial** requires ``Tornado >= 4.2`` for TCP connection. +.. code-block:: bash + + sudo apt-get install android-tools-adb + +.. _Android Debug Bridge (adb): https://developer.android.com/studio/command-line/adb.html -For communication with Android (over USB), also needs ``adb``. +In embedded systems, the WebService Serial will try to download an adb pre-build +if the adb is not installed on the device. -If you uses in a ARM architecture, maybe will be necessary compile -**adb**. In these cases, the project https://github.com/PedalPi/adb-arm -can help you. **adb-arm** PedalPi *fork* already contains some binaries -for RaspberryPi. +Also, is possible compile the adb. See https://github.com/PedalPi/adb-arm How to use ---------- Like described in `Application documentation`_, create a ``start.py`` -and register AndroidController component. +and register WebService Serial component. Is necessary that **WebService Serial** +be registered after the **WebService** (WebService is dependency of WebService Serial +and it will be installed when WebService Serial is installed). .. code:: python @@ -80,106 +84,92 @@ and register AndroidController component. application.stop() +WebService Serial now has been configured to connect with a Android device that are +installed a app compatible with it. If haven't installed a app in your device, is recommended +the `Pedal Pi - Display View`_. With Display View, is possible manages the current pedalboard +quickly by a Android device connected with Pedal Pi by the USB. Read your recommendations for +details how to configure the device to enable the communication between the devices +over USB. + +.. _Pedal Pi - Display View: https://play.google.com/store/apps/details?id=io.github.com.pedalpi.displayview Protocol -------- -The communication are described here. For the possible command lists, -see the `WebService documentation`_. +`WebService Serial` provides a way to communicate with ``WebService`` through a serial connection. -Request -~~~~~~~ +`WebService Serial` provides a TCP client. For communication with a device, is necessary that +the device implements a socket TCP server listening the port ``8888``. -:: +The communication are based in messages from device to the Pedal Pi (``Request`` messages) +and messages from Pedal Pi to the device (``Response`` and ``Event`` messages) - \n - \n - EOF\n +``Request`` Message +~~~~~~~~~~~~~~~~~~~ -- ````: ``int`` that informs the request - Responses will have the same identifier; -- ````: ``GET``, ``POST``, ``PUT``, ``DELETE``, ``SYSTEM`` -- ````: Json data. If none, send ``'{}'`` -- ````: http://pedalpi.github.io/WebService/ -- ``EOF``: The string “EOF”. +With ``Request`` Message, a device can request data. The message format has the following format:: -Example: + \n\nEOF\n :: - 1 PUT /current/bank/1/pedalboard/3 - + + EOF + [empty line here] -Response -~~~~~~~~ - -:: - - RESPONSE - -- ````: ``int``. A response returns the same ``int`` that the request - informs; -- ``RESPONSE``: String ``RESPONSES``; -- ````: Json data. If none, send ``''`` - -Notification -~~~~~~~~~~~~ - -This corresponds the websocket data notifications - -:: - - EVENT +The communication are described here. For the possible command lists, +see the `WebService documentation`_. -- ``EVENT``: String ``EVENT`` -- ````: Json data. If none, send ``''`` +- ````: ``int`` Unique id that defines the request. This value will be used in a response message, identifying the original request message; +- ````: ``string`` Possible values are: -Initialization -~~~~~~~~~~~~~~ + - ``GET``, ``POST``, ``PUT``, ``DELETE`` Based in the `WebService documentation`_; + - ``SYSTEM`` Informs custom system messages. Actually this isn't used; -After the connection has been realized, +- ````: Json data. If none, send an empty string; +- ````: Resource identifier. Is necessary to informs the API version too (``/v1/``). For the full list of resource, see http://pedalpi.github.io/WebService/ +- ``EOF``: The string “EOF”. -1. Application send +Example `Set the current pedalboard`_: :: -:: + 1 PUT /v1/current/bank/1/pedalboard/3 - SYSTEM / - {"message": "connected"} EOF -After initialization -~~~~~~~~~~~~~~~~~~~~ - -The connected device can be request thinks, like: +.. _Set the current pedalboard: http://pedalpi.github.io/WebService/#current-management-manages-the-current-pedalboard-put -- The current pedalboard number - -:: - - GET /v1/current +``Response`` Message +~~~~~~~~~~~~~~~~~~~~ - EOF +``Response`` messages contains a response of a request. For identify the +respective request, see the identifier. The message format has the following format:: -- Response + RESPONSE \n -:: +- ````: ``int`` A response returns the same Unique id that the respective request informs; +- ``RESPONSE``: ``string`` The string “RESPONSE”; +- ````: ``string`` Json encoded data. If none, it will be an empty string; - RESPONSE { "bank": 1, "pedalboard": 0 } +``Event`` Message +~~~~~~~~~~~~~~~~~ -- The pedalboard data +Changes that modify the Pedal Pi event can be applied by others Components. An example is +`Raspberry P0`_, that contains two buttons that when pressed changes the current pedalboard. +To maintain the application integrity, WebService Serial will send ``Event`` messages informing +the changes. -:: +This corresponds the WebService `websocket data notifications`_. - GET /v1/bank/1/pedalboard/0 - {} - EOF +.. _Raspberry P0: https://github.com/PedalPi/Raspberry-P0 +.. _websocket data notifications: http://pedalpi.github.io/WebService/#websocket -- Response +A ``Event`` message format is:: -:: + EVENT \n - RESPONSE { "name": "My pedalboard", "effects": [], "connections": [], "data": {} } +- ``EVENT``: ``string`` The string “EVENT”; +- ````: ``string`` Json encoded data. If none, it will be an empty string; .. _WebService: https://github.com/PedalPi/WebService .. _DisplayView: https://github.com/PedalPi/DisplayView @@ -189,5 +179,6 @@ The connected device can be request thinks, like: Scripts ======= -Install locally to develop -python setup.py develop +Install locally to develop:: + + python setup.py develop From c450b00c04eea7cdac5eb1ae4921a043d28f2885 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Wed, 7 Mar 2018 20:58:28 -0300 Subject: [PATCH 12/21] #21 Improve Readme --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d35378e..62f5311 100644 --- a/README.rst +++ b/README.rst @@ -124,8 +124,8 @@ see the `WebService documentation`_. - ````: ``int`` Unique id that defines the request. This value will be used in a response message, identifying the original request message; - ````: ``string`` Possible values are: - - ``GET``, ``POST``, ``PUT``, ``DELETE`` Based in the `WebService documentation`_; - - ``SYSTEM`` Informs custom system messages. Actually this isn't used; + + ``GET``, ``POST``, ``PUT``, ``DELETE`` Based in the `WebService documentation`_; + + ``SYSTEM`` Informs custom system messages. Actually this isn't used; - ````: Json data. If none, send an empty string; - ````: Resource identifier. Is necessary to informs the API version too (``/v1/``). For the full list of resource, see http://pedalpi.github.io/WebService/ @@ -168,8 +168,8 @@ A ``Event`` message format is:: EVENT \n -- ``EVENT``: ``string`` The string “EVENT”; -- ````: ``string`` Json encoded data. If none, it will be an empty string; +- ``EVENT``: ``string`` The string “EVENT”; +- ````: ``string`` Json encoded data. If none, it will be an empty string; .. _WebService: https://github.com/PedalPi/WebService .. _DisplayView: https://github.com/PedalPi/DisplayView From 6c6c3e6f1b8de970d89d320063703075304f71f2 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 09:00:38 -0300 Subject: [PATCH 13/21] #16 Initial build script --- .gitignore | 2 +- .travis.yml | 22 +++++++++++ README.rst | 8 +++- makefile | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ test/__init__.py | 0 test/example.py | 58 +++++++++++++++++++++++++++++ 6 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 .travis.yml create mode 100644 makefile create mode 100644 test/__init__.py create mode 100644 test/example.py diff --git a/.gitignore b/.gitignore index c3730f1..3382514 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ **.pyc __pycache__/ **.egg-info/ - +test/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f48e3e4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: python + +python: + - "3.4" + - "3.5" + - "3.6" + - "3.7-dev" + - "nightly" + +sudo: required + +install: + - make develop-install-requirements + - make install-tests-requirements + - make install-docs-requirements + - python setup.py develop + +script: + - lv2ls + - make develop-install-requirements + - make test + - #make docs diff --git a/README.rst b/README.rst index 62f5311..3ddd388 100644 --- a/README.rst +++ b/README.rst @@ -176,9 +176,13 @@ A ``Event`` message format is:: .. _Application documentation: http://pedalpi-application.readthedocs.io/en/latest/ .. _WebService documentation: http://pedalpi.github.io/WebService/ -Scripts -======= +Development +=========== Install locally to develop:: python setup.py develop + +See makefile options:: + + make help diff --git a/makefile b/makefile new file mode 100644 index 0000000..fb76392 --- /dev/null +++ b/makefile @@ -0,0 +1,95 @@ +BROWSER=firefox +BOLD=\033[1m +NORMAL=\033[0m + +default: help + +clean: clean-pyc clean-test clean-build clean-docs + +clean-build: + rm -rf .eggs + rm -rf build + rm -rf dist + +clean-pyc: + find . -name '*.pyc' -exec rm --force {} + + find . -name '*.pyo' -exec rm --force {} + + +clean-test: + rm -rf .cache + rm -f .coverage + rm -rf htmlcov + rm -rf test/autosaver_data + +clean-docs: + rm -rf docs/build + +docs: clean-docs + cd docs && $(MAKE) html + +docs-see: docs + $(BROWSER) docs/build/html/index.html + +install-develop-requirements: + sudo apt-get install -y portaudio19-dev python-all-dev --no-install-recommends + sudo apt-get install -y lilv-utils calf-plugins guitarix --no-install-recommends + sudo apt-get install -y lv2-dev --no-install-recommends + pip3 install -U setuptools + pip3 install cffi + +install-docs-requirements: + pip3 install sphinx + pip install sphinx_rtd_theme + +install-tests-requirements: + pip3 install pytest pytest-cov + # For midi tests - https://github.com/x42/midifilter.lv2 + cd /tmp && git clone git://github.com/x42/midifilter.lv2.git && \ + cd midifilter.lv2 && \ + make && \ + sudo make install PREFIX=/usr + +run: + @echo "Run option isn't created =)" + +test: clean-test + mkdir test/autosaver_data + pytest --cov=pluginsmanager + +test-docs: + python -m doctest *.rst -v + python -m doctest docs/*/*.rst -v + +test-details: test + coverage3 html + $(BROWSER) htmlcov/index.html + +help: cabecalho + @echo "" + @echo "Commands" + @echo " $(BOLD)clean$(NORMAL)" + @echo " Clean files" + @echo " $(BOLD)docs$(NORMAL)" + @echo " Make the docs" + @echo " $(BOLD)docs-see$(NORMAL)" + @echo " Make the docs and open it in BROWSER" + @echo " $(BOLD)install-develop-requirements$(NORMAL)" + @echo " Install the develop requirements" + @echo " $(BOLD)install-docs-requirements$(NORMAL)" + @echo " Install the docs requirements" + @echo " $(BOLD)install-tests-requirements$(NORMAL)" + @echo " Install the tests requirements" + @echo " $(BOLD)test$(NORMAL)" + @echo " Execute the tests" + @echo " $(BOLD)test-details$(NORMAL)" + @echo " Execute the tests and shows the result in BROWSER" + @echo " - BROWSER=firefox" + @echo " $(BOLD)help$(NORMAL)" + @echo " Show the valid commands" + +cabecalho: + @echo "$(BOLD)===================" + @echo "> WebService Serial" + @echo "===================" + @echo "" + @echo "Github$(NORMAL): https://pypi.org/project/PedalPi-WebServiceSerial/" diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/example.py b/test/example.py new file mode 100644 index 0000000..c8e8c25 --- /dev/null +++ b/test/example.py @@ -0,0 +1,58 @@ +# Copyright 2017 SrMouraSilva +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pluginsmanager.banks_manager import BanksManager +from pluginsmanager.observer.mod_host.mod_host import ModHost + +from pluginsmanager.model.bank import Bank +from pluginsmanager.model.pedalboard import Pedalboard +from pluginsmanager.model.connection import Connection + +from pluginsmanager.model.lv2.lv2_effect_builder import Lv2EffectBuilder + +from pluginsmanager.model.system.system_effect import SystemEffect + + +#if __name__ == '__main__': +if True: + # Imports application + from application.application import Application + + address = 'localhost' + application = Application(path_data="data/", address=address, test=True) + + # Register WebService before WebServiceSerial + from webservice.webservice import WebService + + application.register(WebService(application)) + + # Register WebServiceSerial after WebService + from webservice_serial.webservice_serial import WebServiceSerial + from webservice_serial.target.android.android_display_view import AndroidDisplayView + + target = AndroidDisplayView() + application.register(WebServiceSerial(application, target)) + + # Start Application + application.start() + + #import tornado + + #try: + # tornado.ioloop.IOLoop.current().start() + #except KeyboardInterrupt: + # application.stop() + + import time + time.sleep(5) From 49f410749b063611074f72b3dc23ad7880859d2d Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 18:59:29 -0300 Subject: [PATCH 14/21] #16 Add very simple coverage support --- .gitignore | 3 ++- .travis.yml | 3 +++ makefile | 4 +--- test/{example.py => example_test.py} | 21 +++++++++++---------- 4 files changed, 17 insertions(+), 14 deletions(-) rename test/{example.py => example_test.py} (76%) diff --git a/.gitignore b/.gitignore index 3382514..9c7d812 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ **.pyc __pycache__/ **.egg-info/ -test/ +data/ +.coverage \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f48e3e4..b30c4f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,6 @@ script: - make develop-install-requirements - make test - #make docs + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/makefile b/makefile index fb76392..094be10 100644 --- a/makefile +++ b/makefile @@ -19,7 +19,6 @@ clean-test: rm -rf .cache rm -f .coverage rm -rf htmlcov - rm -rf test/autosaver_data clean-docs: rm -rf docs/build @@ -53,8 +52,7 @@ run: @echo "Run option isn't created =)" test: clean-test - mkdir test/autosaver_data - pytest --cov=pluginsmanager + pytest --cov=webservice_serial test-docs: python -m doctest *.rst -v diff --git a/test/example.py b/test/example_test.py similarity index 76% rename from test/example.py rename to test/example_test.py index c8e8c25..265a079 100644 --- a/test/example.py +++ b/test/example_test.py @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pluginsmanager.banks_manager import BanksManager -from pluginsmanager.observer.mod_host.mod_host import ModHost +import unittest -from pluginsmanager.model.bank import Bank -from pluginsmanager.model.pedalboard import Pedalboard -from pluginsmanager.model.connection import Connection -from pluginsmanager.model.lv2.lv2_effect_builder import Lv2EffectBuilder - -from pluginsmanager.model.system.system_effect import SystemEffect +#class ControllerTest(unittest.TestCase): +# +# def test_all(self): +# test() -#if __name__ == '__main__': -if True: +def test(): # Imports application from application.application import Application @@ -42,6 +38,7 @@ from webservice_serial.target.android.android_display_view import AndroidDisplayView target = AndroidDisplayView() + application.register(WebServiceSerial(application, target)) # Start Application @@ -56,3 +53,7 @@ import time time.sleep(5) + + +if __name__ == '__main__': + test() From 46fb8610868642ca004b8496be0863d7f0fee33c Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 19:46:57 -0300 Subject: [PATCH 15/21] #16 Fix travis script --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b30c4f2..651706b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ python: sudo: required install: - - make develop-install-requirements + - make install-develop-requirements - make install-tests-requirements - make install-docs-requirements - python setup.py develop From 51d6ed2ab0d2b843e3dbafc52b2751a2282dadd7 Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 19:54:15 -0300 Subject: [PATCH 16/21] #16 Fix test to ws v0.3.0t --- setup.py | 2 +- test/example_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d1e284b..0151609 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def readme(): package_data={}, install_requires=[ - 'PedalPi-WebService>=0.3.0', + 'PedalPi-WebService=0.3.0', ], classifiers=[ diff --git a/test/example_test.py b/test/example_test.py index 265a079..7225477 100644 --- a/test/example_test.py +++ b/test/example_test.py @@ -31,7 +31,7 @@ def test(): # Register WebService before WebServiceSerial from webservice.webservice import WebService - application.register(WebService(application)) + application.register(WebService(application, port=3000)) # Register WebServiceSerial after WebService from webservice_serial.webservice_serial import WebServiceSerial From fc471761e859b628c3130b75d373d3ba177db1ac Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 19:58:17 -0300 Subject: [PATCH 17/21] #16 Try fix install requirements. Add Readme badges --- README.rst | 9 +++++++++ setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3ddd388..a8bbc04 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,15 @@ WebService Serial ================= +.. image:: https://travis-ci.org/PedalPi/WebServiceSerial.svg?branch=master + :target: https://travis-ci.org/PedalPi/WebServiceSerial + :alt: Build Status + +.. image:: https://codecov.io/gh/PedalPi/WebServiceSerial/branch/master/graph/badge.svg + :target: https://codecov.io/gh/PedalPi/WebServiceSerial + :alt: Code coverage + + WebService Serial disposes the `WebService`_ communication via TCP Serial communication. diff --git a/setup.py b/setup.py index 0151609..4746533 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def readme(): package_data={}, install_requires=[ - 'PedalPi-WebService=0.3.0', + 'PedalPi-WebService==0.3.0', ], classifiers=[ From 0d8a8d5cfcd40a9063553e8983eaca53f17a04bc Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 20:04:42 -0300 Subject: [PATCH 18/21] #16 Try fix bugs --- .travis.yml | 2 -- makefile | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 651706b..a7855ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,7 @@ install: script: - lv2ls - - make develop-install-requirements - make test - - #make docs after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/makefile b/makefile index 094be10..5a35814 100644 --- a/makefile +++ b/makefile @@ -35,6 +35,7 @@ install-develop-requirements: sudo apt-get install -y lv2-dev --no-install-recommends pip3 install -U setuptools pip3 install cffi + sudo apt-get install -y android-tools-adb --no-install-recommends install-docs-requirements: pip3 install sphinx From a06fe170eb20f1c3c54e41683b0104391dfe4ffd Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 21:30:32 -0300 Subject: [PATCH 19/21] #22 Auto dettect if adb has installed --- CHANGES | 6 ++- webservice_serial/target/android/adb.py | 17 +++++++- .../target/android/android_display_view.py | 43 ++++++++++++++++--- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 14369d9..ffbe5f8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -Version 0.2.0 - released mm/dd/18 +Version 0.2.0 - released 03/dd/18 ================================= + - Initial release + - Change Android Controller -> WebServiceSerial: Now use WebService to reuse WebService methods - Defined Target: Initial target is Android, but is possible implements other targets for communication with other devices, as Arduino via USB Serial communication - Try reconnect when connection is closed + - Check if is ADB has installed + - Try download the ADB if isn't installed diff --git a/webservice_serial/target/android/adb.py b/webservice_serial/target/android/adb.py index e82a57e..c1c8a2f 100644 --- a/webservice_serial/target/android/adb.py +++ b/webservice_serial/target/android/adb.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import subprocess class Adb(object): @@ -28,8 +29,7 @@ def __init__(self, command="adb", log=None): self.log = log def start(self, port, activity): - self.execute('shell am start -n {}'.format(activity)) - #FIXME delay? + #self.execute('shell am start -n {}'.format(activity)) #self.execute('forward --remove-all') self.execute('forward tcp:{} tcp:{}'.format(port, port)) @@ -42,3 +42,16 @@ def execute(self, command): def close(self, port): self.execute('forward --remove tcp:{}'.format(port)) + + @staticmethod + def has_installed(): + """ + Check if the current system have the ``adb`` installed + :return: + """ + try: + subprocess.call(["adb"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + return False + + return True diff --git a/webservice_serial/target/android/android_display_view.py b/webservice_serial/target/android/android_display_view.py index ebd70b5..b0d4cd3 100644 --- a/webservice_serial/target/android/android_display_view.py +++ b/webservice_serial/target/android/android_display_view.py @@ -12,8 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from webservice_serial.target.target import Target +import os +import subprocess + from webservice_serial.target.android.adb import Adb +from webservice_serial.target.target import Target class AndroidDisplayView(Target): @@ -21,18 +24,48 @@ class AndroidDisplayView(Target): :param string adb_command: Command that call the Android Debug Bridge In Raspberry maybe be a `./adb` executable file """ - activity = 'io.github.pedalpi.pedalpi_display/io.github.pedalpi.displayview.MainActivity' + activity = 'io.github.pedalpi.pedalpi_display/io.github.pedalpi.displayview.activity.ResumeActivity' - def __init__(self, adb_command="adb"): + def __init__(self): super(AndroidDisplayView, self).__init__() self.adb = None - self.adb_command = adb_command def init(self, application, port): super(AndroidDisplayView, self).init(application, port) - self.adb = Adb(self.adb_command, application.log) + adb_command = self._discover_adb_command() + self.application.log('AndroidDisplayView - Android Debug Bridge command "{}"', adb_command) + + self.adb = Adb(adb_command, application.log) self.adb.start(port, AndroidDisplayView.activity) def close(self): self.adb.close(self.port) + + def _discover_adb_command(self): + if Adb.has_installed(): + return "adb" + + path = self.application.path_data / "adb" + if not path.is_file(): + self.application.log("AndroidDisplayView - Downloading adb pre-compiled") + self._download_adb(path) + + return path + + def _download_adb(self, path): + if self._version() == 'Raspberry 3': + command = "wget -O {} https://github.com/PedalPi/adb-arm/raw/master/adb-rpi3".format(path) + else: + command = "wget -O {} https://github.com/PedalPi/adb-arm/raw/master/adb-arm-binary".format(path) + + subprocess.call(command.split()) + subprocess.call("chmod +x {}".format(path).split()) + + def _version(self): + command = 'cat /sys/firmware/devicetree/base/model' + + if 'Raspberry Pi 3' in subprocess.check_output(command).decode('UTF-8').split('\n')[0]: + return 'Raspberry Pi 3' + + return "" From 78c551e8ec8f3029f525da1418031686e499268a Mon Sep 17 00:00:00 2001 From: SrMouraSilva Date: Thu, 8 Mar 2018 21:33:47 -0300 Subject: [PATCH 20/21] Issue #22 Remove old imports and doc --- webservice_serial/protocol/request_message.py | 2 -- webservice_serial/target/android/android_display_view.py | 6 +----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/webservice_serial/protocol/request_message.py b/webservice_serial/protocol/request_message.py index 00d4bec..28a0193 100644 --- a/webservice_serial/protocol/request_message.py +++ b/webservice_serial/protocol/request_message.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json - class RequestMessage(object): """ diff --git a/webservice_serial/target/android/android_display_view.py b/webservice_serial/target/android/android_display_view.py index b0d4cd3..b265efb 100644 --- a/webservice_serial/target/android/android_display_view.py +++ b/webservice_serial/target/android/android_display_view.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import subprocess from webservice_serial.target.android.adb import Adb @@ -20,10 +19,6 @@ class AndroidDisplayView(Target): - """ - :param string adb_command: Command that call the Android Debug Bridge - In Raspberry maybe be a `./adb` executable file - """ activity = 'io.github.pedalpi.pedalpi_display/io.github.pedalpi.displayview.activity.ResumeActivity' def __init__(self): @@ -47,6 +42,7 @@ def _discover_adb_command(self): return "adb" path = self.application.path_data / "adb" + if not path.is_file(): self.application.log("AndroidDisplayView - Downloading adb pre-compiled") self._download_adb(path) From 718c0ea815a4327e9e8672a14948ce2d77d9c029 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Thu, 8 Mar 2018 21:42:51 -0300 Subject: [PATCH 21/21] Revert "Issue 19 android usb accessory" --- webservice_serial/aoa/__init__.py | 0 webservice_serial/aoa/pyaoa.py | 18 --------- webservice_serial/aoa/usb_devices.py | 53 -------------------------- webservice_serial/aoa/usv_events.py | 56 ---------------------------- 4 files changed, 127 deletions(-) delete mode 100644 webservice_serial/aoa/__init__.py delete mode 100644 webservice_serial/aoa/pyaoa.py delete mode 100644 webservice_serial/aoa/usb_devices.py delete mode 100644 webservice_serial/aoa/usv_events.py diff --git a/webservice_serial/aoa/__init__.py b/webservice_serial/aoa/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/webservice_serial/aoa/pyaoa.py b/webservice_serial/aoa/pyaoa.py deleted file mode 100644 index f2732a4..0000000 --- a/webservice_serial/aoa/pyaoa.py +++ /dev/null @@ -1,18 +0,0 @@ -from core import * - -dev = find_device([(0xfce, 0x518c)]) -#toggle_accessory_mode(dev, "PedalPi", "Display-View", "Description", "1.0", "http://www.github.com/PedalPi/DisplayView", "SN-123-456-789") -#toggle_accessory_mode(dev, "Manufacturer", "Model", "Description", "1.0", "http://www.github.com/PedalPi/DisplayView", "SN-123-456-789") - -MANUFACTURER = "Pedal Pi" -MODEL_NAME = "Display View" -DESCRIPTION = "Pedal Pi - Display View" -VERSION = "0.3.0" -URL = "http://github.com/PedalPi/DisplayView" -SERIAL_NUMBER = "0001" - -toggle_accessory_mode(dev, MANUFACTURER, MODEL_NAME, DESCRIPTION, VERSION, URL, SERIAL_NUMBER) -dev = find_accessory() - -import time -time.sleep(100) \ No newline at end of file diff --git a/webservice_serial/aoa/usb_devices.py b/webservice_serial/aoa/usb_devices.py deleted file mode 100644 index e429012..0000000 --- a/webservice_serial/aoa/usb_devices.py +++ /dev/null @@ -1,53 +0,0 @@ -from pyudev import Context, Monitor, MonitorObserver - -context = Context() -''' -monitor = Monitor.from_netlink(context) -monitor.filter_by(subsystem='input') -def print_device_event(device): - print('background event {0.action}: {0.device_path}'.format(device)) -observer = MonitorObserver(monitor, callback=print_device_event, name='monitor-observer') -print(observer.daemon) -observer.start() - -from pyudev import Context, Monitor -context = Context() -monitor = Monitor.from_netlink(context) -monitor.filter_by('input') -device = monitor.poll(timeout=1) -if device: - print('{0.action}: {0}'.format(device)) -''' -import time - -#while True: -# time.sleep(1) - -''' -import pyudev -context = pyudev.Context() -monitor = Monitor.from_netlink(context) -# For USB devices -monitor.filter_by(subsystem='usb') -# OR specifically for most USB serial devices -#monitor.filter_by(subsystem='tty') -for action, device in monitor: - vendor_id = device.get('ID_VENDOR_ID') - # I know the devices I am looking for have a vendor ID of '22fa' - print(vendor_id) - #if vendor_id in ['22fa']: - # print('Detected {} for device with vendor ID {}'.format(action, vendor_id)) -''' - -import usb.core -# find USB devices -dev = usb.core.find(find_all=True) -# loop through devices, printing vendor and product ids in decimal and hex -for cfg in dev: - try: - print(cfg.manufacturer) - except Exception: - print("error") - print('Decimal VendorID=' + str(cfg.idVendor) + ' & ProductID=' + str(cfg.idProduct)) - print('Hexadecimal VendorID=' + hex(cfg.idVendor) + ' & ProductID=' + hex(cfg.idProduct)) - print() \ No newline at end of file diff --git a/webservice_serial/aoa/usv_events.py b/webservice_serial/aoa/usv_events.py deleted file mode 100644 index 8663768..0000000 --- a/webservice_serial/aoa/usv_events.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/python -# accessory.py -# License GPLv2 -# (c) Manuel Di Cerbo, Nexus-Computing GmbH -# https://github.com/rosterloh/pyusb-android/blob/master/accessory.py -import os -import socket - - -NETLINK_KOBJECT_UEVENT = 15 - - -def main(): - while True: - sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_KOBJECT_UEVENT) - sock.bind((os.getpid(), -1)) - - while True: - data = sock.recv(512) - vid = parse_uevent(data) - if vid is not None: - break - - sock.close() - accessory_task(vid) - - -def parse_uevent(data): - """ - :param bytes data: - :return: - """ - - lines = data.decode('UTF8', 'ignore').split('\0') - - #lines = data.decode('UTF8').split('\0')#.replace('\fe', b'').decode('UTF8') - print(lines) - keys = [] - for line in lines: - val = line.split('=') - if len(val) == 2: - keys.append((val[0], val[1])) - - attributes = dict(keys) - if 'ACTION' in attributes and 'PRODUCT' in attributes: - if attributes['ACTION'] == 'add': - parts = attributes['PRODUCT'].split('/') - return int(parts[0], 16) - - return None - -def accessory_task(teste): - print(teste) - -if __name__ == '__main__': - main() \ No newline at end of file