Skip to content

Commit

Permalink
Windowed application (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
pauloesteban committed Aug 21, 2022
1 parent b6c3034 commit 582a00c
Show file tree
Hide file tree
Showing 9 changed files with 622 additions and 81 deletions.
102 changes: 70 additions & 32 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,79 @@ name: Python application

on:
push:
branches:
- main
pull_request:
branches: [ main ]
# branches:
# - develop
tags:
- 'v*'
# pull_request:
# branches: [ main ]

jobs:
create_release:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
name: ${{ github.ref_name }}
draft: false
prerelease: false
generate_release_notes: false

build:
runs-on: ${{ matrix.os }}
name: Build Release
needs: create_release
strategy:
matrix:
os: [macos-12, macos-latest, macos-10.15, windows-latest, ubuntu-latest]

os: [macos-latest, windows-latest]
include:
- os: macos-latest
release_suffix: mac.tar.xz
- os: windows-latest
release_suffix: windows.tar.gz
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install bleak python-osc pyinstaller
- name: Build with PyInstaller
run: |
pyinstaller -F metabow_bridge.py
- name: Archive and compress files
run: tar -czvf metabow_bridge_${{ matrix.os }}.tar.gz -C ${{ runner.workspace }}/sensor-tile-osc/dist/ .
# - uses: actions/upload-artifact@v2
# with:
# name: metabow_bridge_${{ matrix.os }}
# path: metabow_bridge_${{ matrix.os }}.tar.gz
- uses: xresloader/upload-to-github-release@master
env:
GITHUB_TOKEN: ${{ github.token }}
with:
file: "*.tar.gz"
tags: false
verbose: true
- name: Checkout Code
uses: actions/checkout@v3

- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"

- name: Install dependencies
run: pip install -U wheel numpy pyquaternion bleak python-osc pyinstaller

- name: Build executable
run: pyinstaller --clean metabow_bridge.spec
# tar -czvf metabow_bridge_${{ matrix.release_suffix }} -C ${{ runner.workspace }}/sensor-tile-osc/dist/ .

- name: Mac archive and compress
if: matrix.os == 'macos-latest'
run: tar -cJf metabow_bridge_${{ matrix.release_suffix }} -C ${{ runner.workspace }}/sensor-tile-osc/dist/ bridge.app

- name: Windows archive and compress
if: matrix.os == 'windows-latest'
run: tar -czf metabow_bridge_${{ matrix.release_suffix }} -C ${{ runner.workspace }}/sensor-tile-osc/dist/ .

- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.create_release.outputs.tag-name }}
files: metabow_bridge_${{ matrix.release_suffix }}

# - uses: actions/upload-artifact@v2
# with:
# name: metabow_bridge_${{ matrix.os }}
# path: metabow_bridge_${{ matrix.os }}.tar.gz

# - name: Upload to GH release
# uses: xresloader/upload-to-github-release@master
# env:
# GITHUB_TOKEN: ${{ github.token }}
# with:
# file: "*.tar.gz"
# tags: false
# verbose: true
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ MANIFEST
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
Expand Down
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
# sensor-tile-osc
# SensorTile Bridge App

Cross-platform console application to pair STEVAL-STLCX01V1 BLE devices to send sensor data via OSC (Open Sound Control) message.
Cross-platform application to pair STEVAL-STLCX01V1 BLE devices to send sensor data via OSC (Open Sound Control) messages.

## Installation

Download the compressed file accordingly from the [Releases](https://github.com/pauloesteban/sensor-tile-osc/releases) page.

### Windows

- Uncompress the `tar.gz` file on a folder of your preference.
- Add a Windows security [exclusion](https://support.microsoft.com/en-us/windows/add-an-exclusion-to-windows-security-811816c0-4dfd-af4a-47e4-c301afe13b26) to the `bridge` folder.
- Execute `bridge.exe`
### macOS

- Uncompress the `tar.xz` file on a folder of your preference.
- Using a terminal application, remove quarantine from the `bridge.app`

```
sudo xattr -r -d com.apple.quarantine bridge.app
```

## References

- [MP730026 DMM BLE Tutorial using Python](https://www.element14.com/community/community/element14-presents/workbenchwednesdays/blog/2020/03/09/connecting-to-mp730026-ble-dmm-with-python-and-bleak)

## Credits

Conducted by Roberto Alonso Trillo.
HKBU Department of Music.
Developed by Paulo Chiliguano, Travis West and KA HO Wong.

Conducted by Roberto Alonso Trillo @ HKBU Department of Music.
38 changes: 38 additions & 0 deletions calibrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@


# Modified by Paulo Chiliguano and Travis West
# Directed by Dr Roberto Alonso Trillo
# Department of Music - Hong Kong Baptist University
# 2022

"""Calibrator
"""
import numpy as np


class Calibrator():
def __init__(self):
self.DEG_RAD_RATIO = np.pi / 180.0
self.accl_bias = np.zeros(3)
self.gyro_bias = np.zeros(3)
self.magn_bias = np.zeros(3)
self.accl_transform = 0.001 * np.identity(3)

# NOTE: the gyro is not yet calibrated in pyMIMU
self.gyro_transform = self.DEG_RAD_RATIO * np.identity(3)

self.magn_transform = np.identity(3)

def calibrate(self, vector_measurement, matrix_transform, vector_bias):
return np.matmul(matrix_transform, vector_measurement - vector_bias)

def calibrate_accl(self, accl):
return self.calibrate(accl, self.accl_transform, self.accl_bias)

def calibrate_gyro(self, gyro):
return self.calibrate(gyro, self.gyro_transform, self.gyro_bias)

def calibrate_magn(self, magn):
return self.calibrate(magn, self.magn_transform, self.magn_bias)

#TODO: Set biases function
118 changes: 118 additions & 0 deletions fusion_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@


# Modified by Paulo Chiliguano and Travis West
# Directed by Dr Roberto Alonso Trillo
# Department of Music - Hong Kong Baptist University
# 2022

"""Fusion filter
"""
import numpy as np
from time import monotonic
from pyquaternion import Quaternion


class BaseFilter:
def __init__(self):
self.period = 0
self.current = monotonic()
self.previous = self.current
self.q = Quaternion()
self.q_align = Quaternion()

def calculate_period(self):
self.current = monotonic()
self.period = self.current - self.previous
self.previous = self.current

return self.period

def set_alignment(self):
self.q_align = self.q.conjugate

def align(self, quaternion):
return self.q_align * quaternion

def fuse(self, omega, v_a, v_m, k_I = 1.0, k_P = 3.0, k_a=1.0, k_m=1.0):
return Quaternion()

def fuse_and_align(self, *args):
q = self.fuse(*args)
return self.align(q), self.q_align.rotate(self.ma_earth)


class FusionFilter(BaseFilter):
def __init__(self):
super().__init__()
self.static_threshold = 0.01
self.galpha = 0.93
self.b_hat = np.zeros(3)
self.h = np.zeros(3)
self.b = np.zeros(3)
self.ma_earth = np.zeros(3)
self.ma_sensor = np.zeros(3)
self.v_hat_x = np.zeros(3)
self.v_hat_a = np.zeros(3)
self.w_a = np.zeros(3)
self.w_x = np.zeros(3)
self.w_mes = np.zeros(3)
self.da = np.zeros(3)
self.anm1 = np.zeros(3)
self.aa = np.zeros(3)
self.gyro_bias = np.zeros(3)


def set_gyro_bias_alpha(self, v):
self.galpha = v

def set_static_threshold(self, v):
self.static_threshold = v

def fuse(self, omega, v_a, v_m, k_I = 0.0, k_P = 3.0, k_a=1.0, k_m=0.0):
self.calculate_period()

self.aa = self.anm1 * 0.5 + self.aa * 0.5 # average accel
self.da = (self.aa - self.anm1) # "derivative" accel
self.static = np.linalg.norm(self.da) < self.static_threshold
self.anm1 = v_a

if self.static:
self.gyro_bias = (1 - self.galpha) * omega + self.galpha * self.gyro_bias
omega -= self.gyro_bias

rotation = self.q.rotation_matrix # the basis axes of the sensor frame expressed in the global frame
inverse = self.q.conjugate.rotation_matrix # the basis axes of the global frame expressed in the sensor frame

# inverse[2] represents the global Z axis (related to gravity) expressed in the sensor coordinate frame
self.ma_sensor = v_a - inverse[:,2] # zero-g in sensor frame
self.ma_earth = np.matmul(inverse, self.ma_sensor) # zero-g in global frame

v_a = v_a / np.linalg.norm(v_a)
v_m = v_m / np.linalg.norm(v_m)
self.v_x = np.cross(v_a, v_m) # cross-product of accel and magnetic field (cross vector)

self.h = np.matmul(rotation, self.v_x)
self.b = np.array([np.sqrt(self.h[0]*self.h[0] + self.h[1]*self.h[1]), 0, self.h[2]])

self.v_hat_a = inverse[:,2] # estimated gravity vector
self.v_hat_x = np.matmul(inverse, self.b) # estimated cross vector

# the cross product $a x b$ gives a vector that points along the axis of rotation to move
# the lh operand to the rh operand, with a magnitude $||a x b|| = ||a|| ||b|| |sin(\theta)|$
# where $\theta$ is the angle between the vectors. When the vectors are normalized and the
# angle is nearly zero, $a x b$ approximates the rotation vector from a to b.
# see also eqs. (32c) and (48a) in Mahoney et al. 2008
self.w_a = np.cross(v_a, self.v_hat_a) # discrepancy based on accelerometer reading
self.w_x = np.cross(self.v_x, self.v_hat_x) # discrepancy based on cross vector
self.w_mes = self.w_a * k_a + self.w_x * k_m

# error correction is added to omega (angular rate) before integrating
if k_I > 0.0:
self.b_hat += self.static * self.w_mes * self.period # see eq. (48c)
omega += k_P * self.w_mes + k_I * self.b_hat
else:
self.b_hat = np.zeros(3) # Madgwick: "prevent integral windup"
omega += k_P * self.w_mes

self.q.integrate(omega, self.period)
return self.q
45 changes: 45 additions & 0 deletions gesture_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@


# Modified by Paulo Chiliguano and Travis West
# Directed by Dr Roberto Alonso Trillo
# Department of Music - Hong Kong Baptist University
# 2022

"""Gesture model
"""
import numpy as np
from pyquaternion import Quaternion
from typing import List
from calibrator import Calibrator
from fusion_filter import FusionFilter


class GestureModel():
def __init__(self):
self.RAD_DEG_RATIO = 180 / np.pi
self.skewness = 0
self.tilt = 0
self.roll = 0
self.caccl = np.zeros(3)
self.cgyro = np.zeros(3)
self.cmagn = np.zeros(3)
self.matrix = np.identity(3)
self.movement_acceleration = np.zeros(3)
self.acceleration_derivative = np.zeros(3)
self.movement_velocity = np.zeros(3)
self.quaternion = Quaternion()
self.calibrator = Calibrator()
self.fusion_filter = FusionFilter()

def tick(self, accl: List[float], gyro: List[float], magn: List[float]):
self.caccl = self.calibrator.calibrate_accl(accl)
self.cgyro = self.calibrator.calibrate_gyro(gyro)
self.cmagn = self.calibrator.calibrate_magn(magn)
self.quaternion = self.fusion_filter.fuse(self.cgyro, self.caccl, self.cmagn)
self.matrix = self.quaternion.rotation_matrix
self.skewness = self.RAD_DEG_RATIO * np.arctan2(self.matrix[1,0], self.matrix[0,0])
self.tilt = self.RAD_DEG_RATIO * np.arctan2(self.matrix[2,0], np.sqrt(self.matrix[2,1] * self.matrix[2,1] + self.matrix[2,2] * self.matrix[2,2]))
self.roll = self.RAD_DEG_RATIO * np.arctan2(self.matrix[2,1], self.matrix[2,2])
self.movement_acceleration = self.fusion_filter.ma_sensor
self.acceleration_derivative = self.fusion_filter.da
self.movement_velocity = self.movement_velocity * 0.9999 + self.movement_acceleration

0 comments on commit 582a00c

Please sign in to comment.