diff --git a/examples/labs/orientation_controller/index.html b/examples/labs/orientation_controller/index.html
new file mode 100644
index 000000000..9def4177a
--- /dev/null
+++ b/examples/labs/orientation_controller/index.html
@@ -0,0 +1,26 @@
+{% extends "base-controller.html" %}
+
+{% block main %}
+
+
+
+ {% if g.show_left_joystick %}
+
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/examples/labs/orientation_controller/main.py b/examples/labs/orientation_controller/main.py
new file mode 100644
index 000000000..95b67c763
--- /dev/null
+++ b/examples/labs/orientation_controller/main.py
@@ -0,0 +1,44 @@
+import os
+
+from pitop import Camera, DriveController, Pitop
+from pitop.labs import RoverWebController
+
+# to access device orientation sensors, the webpage must be accessed over ssl,
+# so ensure that an ssl cert exists for this
+if not os.path.exists("cert.pem") or not os.path.exists("key.pem"):
+ print("Generating self-signed ssl cert")
+ os.system(
+ 'openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -nodes -subj "/O=pi-top"'
+ )
+
+rover = Pitop()
+rover.add_component(DriveController())
+rover.add_component(Camera())
+
+
+def device_orientation(data):
+ print(data)
+ x = data.get("x", 0)
+ y = data.get("y", 0)
+
+ if abs(x) < 5:
+ x = 0
+ x = x * -0.1
+
+ if abs(y) < 5:
+ y = 0
+ y = y * 0.1
+
+ print(x, y)
+ rover.drive.robot_move(y, x)
+
+
+rover_controller = RoverWebController(
+ get_frame=rover.camera.get_frame,
+ drive=rover.drive,
+ message_handlers={"device_orientation": device_orientation},
+ cert="cert.pem",
+ key="key.pem",
+)
+
+rover_controller.serve_forever()
diff --git a/pitop/labs/web/blueprints/messaging/__init__.py b/pitop/labs/web/blueprints/messaging/__init__.py
index 6b570bff2..d015922c1 100644
--- a/pitop/labs/web/blueprints/messaging/__init__.py
+++ b/pitop/labs/web/blueprints/messaging/__init__.py
@@ -64,6 +64,10 @@ def send(response_message):
if message:
handle_message(message, send)
+ disconnect_handler = message_handlers.get("disconnect")
+ if disconnect_handler:
+ disconnect_handler()
+
del self.sockets[id]
def register(self, app, options, *args):
diff --git a/pitop/labs/web/blueprints/rover/__init__.py b/pitop/labs/web/blueprints/rover/__init__.py
index 8fafc18ef..72ccf394a 100644
--- a/pitop/labs/web/blueprints/rover/__init__.py
+++ b/pitop/labs/web/blueprints/rover/__init__.py
@@ -49,6 +49,13 @@ def right_joystick(data):
message_handlers["right_joystick"] = right_joystick
+ if message_handlers.get("disconnect") is None:
+
+ def disconnect():
+ drive.robot_move(0, 0)
+
+ message_handlers["disconnect"] = disconnect
+
self.controller_blueprint = ControllerBlueprint(
get_frame=get_frame, message_handlers=message_handlers
)
diff --git a/pitop/labs/web/blueprints/rover/templates/base-rover.html b/pitop/labs/web/blueprints/rover/templates/base-rover.html
index 57c8638cb..fdfb537fd 100644
--- a/pitop/labs/web/blueprints/rover/templates/base-rover.html
+++ b/pitop/labs/web/blueprints/rover/templates/base-rover.html
@@ -7,7 +7,6 @@
{% if g.show_left_joystick %}
{% endif %}
diff --git a/pitop/labs/web/blueprints/webcomponents/templates/setup-webcomponents.html b/pitop/labs/web/blueprints/webcomponents/templates/setup-webcomponents.html
index 243c438ac..67ca32e84 100644
--- a/pitop/labs/web/blueprints/webcomponents/templates/setup-webcomponents.html
+++ b/pitop/labs/web/blueprints/webcomponents/templates/setup-webcomponents.html
@@ -1,2 +1,3 @@
+
diff --git a/pitop/labs/web/blueprints/webcomponents/webcomponents/orientation-component.js b/pitop/labs/web/blueprints/webcomponents/webcomponents/orientation-component.js
new file mode 100644
index 000000000..412ff0c87
--- /dev/null
+++ b/pitop/labs/web/blueprints/webcomponents/webcomponents/orientation-component.js
@@ -0,0 +1,138 @@
+class DeviceOrientation extends HTMLElement {
+ constructor() {
+ super();
+ }
+
+ connectedCallback() {
+ if (!this.connected) {
+ this.connected = true;
+ this.setup();
+ }
+ }
+
+ disconnectedCallback() {
+ this.disable();
+ this.connected = false;
+ }
+
+ setup = async () => {
+ this.attachShadow({mode: 'open'});
+ this.wrapper = document.createElement('div');
+ this.shadowRoot.append(this.wrapper);
+
+ this.header = document.createElement('h3');
+ this.header.textContent = 'Device Orientation Control';
+ this.wrapper.appendChild(this.header);
+
+ if (typeof DeviceOrientationEvent === 'undefined') {
+ this.header.textContent = 'Device Orientation Not Supported On This Device!';
+ return;
+ }
+
+ if (typeof DeviceOrientationEvent.requestPermission === 'function') {
+ this.permissionButton = document.createElement('button');
+ this.permissionButton.setAttribute('type','button');
+ this.permissionButton.textContent = 'Allow sensor access';
+ this.permissionButton.addEventListener('click', this.requestPermission);
+ this.wrapper.appendChild(this.permissionButton);
+ return;
+ }
+
+ this.showEnable();
+ }
+
+ requestPermission = async () => {
+ const permissionState = await DeviceOrientationEvent.requestPermission()
+ return permissionState === 'granted' ? this.permissionGranted() : this.permissionDenied();
+ }
+
+ permissionGranted = () => {
+ this.showEnable();
+ this.permissionButton.remove();
+ }
+
+ permissionDenied = () => {
+ this.header.textContent = 'Device Orientation Permission Denied!';
+ }
+
+ showEnable = () => {
+ this.enabled = document.createElement('input');
+ this.enabled.setAttribute('type','checkbox');
+ this.enabled.setAttribute('id','enabled');
+ this.wrapper.appendChild(this.enabled);
+
+ this.enabledLabel = document.createElement('label');
+ this.enabledLabel.setAttribute('for','enabled');
+ this.enabledLabel.innerText = 'Disabled';
+ this.wrapper.appendChild(this.enabledLabel);
+
+ this.enabled.addEventListener('change', (event) => {
+ if (event.currentTarget.checked) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ })
+ }
+
+ enable = () => {
+ window.addEventListener('deviceorientation', this.handleOrientationEvent);
+ // deviceorientation events don't fire when window blurs, so reset controls
+ window.addEventListener('blur', this.orientationReset);
+
+ this.showOrientationDisplay();
+ this.enabledLabel.innerText = 'Enabled';
+ }
+
+ disable = () => {
+ this.orientationReset();
+
+ window.removeEventListener('deviceorientation', this.handleOrientationEvent);
+ window.removeEventListener('blur', this.orientationReset);
+
+ this.hideOrientationDisplay();
+ this.enabledLabel.innerText = 'Disabled';
+ }
+
+ orientationReset = () => {
+ this.handleOrientationEvent({ alpha: 0, beta: 0, gamma: 0 });
+ };
+
+ handleOrientationEvent = (event) => {
+ const x = event.beta; // landscape left right
+ const y = event.gamma; // landscape forward back
+ const z = event.alpha; // landscape rotation
+
+ this.updateOrientationDisplay(x, y, z);
+
+ eval(`
+ const data = ${JSON.stringify({ x, y, z })};
+ ${this.getAttribute('onchange')}
+ `);
+ }
+
+ showOrientationDisplay = () => {
+ this.x = document.createElement('p');
+ this.y = document.createElement('p');
+ this.z = document.createElement('p');
+ this.wrapper.appendChild(this.x);
+ this.wrapper.appendChild(this.y);
+ this.wrapper.appendChild(this.z);
+
+ this.updateOrientationDisplay(0, 0, 0)
+ }
+
+ hideOrientationDisplay = () => {
+ this.x.remove();
+ this.y.remove();
+ this.z.remove();
+ }
+
+ updateOrientationDisplay = (x, y, z) => {
+ this.x.textContent = 'x: ' + x;
+ this.y.textContent = 'y: ' + y;
+ this.z.textContent = 'z: ' + z;
+ }
+}
+
+window.customElements.define('orientation-component', DeviceOrientation);
diff --git a/pitop/labs/web/webcontroller.py b/pitop/labs/web/webcontroller.py
index ba2664621..3dfcaad06 100644
--- a/pitop/labs/web/webcontroller.py
+++ b/pitop/labs/web/webcontroller.py
@@ -24,6 +24,8 @@ def __init__(
pan_tilt=None,
message_handlers={},
blueprints=[],
+ cert=None,
+ key=None,
**kwargs
):
self.rover_blueprint = RoverControllerBlueprint(
@@ -34,7 +36,11 @@ def __init__(
)
WebServer.__init__(
- self, blueprints=[self.rover_blueprint] + blueprints, **kwargs
+ self,
+ blueprints=[self.rover_blueprint] + blueprints,
+ cert=cert,
+ key=key,
+ **kwargs
)
def broadcast(self, message):
diff --git a/pitop/labs/web/webserver.py b/pitop/labs/web/webserver.py
index b09afaedf..9577f1e14 100644
--- a/pitop/labs/web/webserver.py
+++ b/pitop/labs/web/webserver.py
@@ -48,32 +48,47 @@ def add_header(req):
class WebServer(WSGIServer):
- def __init__(self, port=8070, app=create_app(), blueprints=[BaseBlueprint()]):
+ def __init__(
+ self,
+ port=8070,
+ app=create_app(),
+ blueprints=[BaseBlueprint()],
+ cert=None,
+ key=None,
+ ):
self.port = port
self.app = app
self.sockets = Sockets(app)
+ self.ssl = cert and key is not None
with self.app.app_context():
for blueprint in blueprints:
self.app.register_blueprint(blueprint, sockets=self.sockets)
WSGIServer.__init__(
- self, ("0.0.0.0", port), self.app, handler_class=WebSocketHandler
+ self,
+ ("0.0.0.0", port),
+ self.app,
+ handler_class=WebSocketHandler,
+ certfile=cert,
+ keyfile=key,
)
def _log_address(self):
+ protocol = "http" if not self.ssl else "https"
+
ip_addresses = list()
for interface in ("wlan0", "ptusb0", "lo", "en0"):
ip_address = get_internal_ip(interface)
- if is_url("http://" + ip_address):
+ if is_url(f"{protocol}://{ip_address}"):
ip_addresses.append(ip_address)
print("WebServer is listening at:")
if len(ip_addresses) > 0:
for ip_address in ip_addresses:
- print(f"\t- http://{ip_address}:{self.port}/")
+ print(f"\t- {protocol}://{ip_address}:{self.port}/")
else:
- print(f"\t- http://localhost:{self.port}/ (on same device)")
+ print(f"\t- {protocol}://localhost:{self.port}/ (on same device)")
def start(self):
self._log_address()