Everything integrated, simple graphing

This commit is contained in:
Paweł Sadowski 2025-01-15 11:41:19 +00:00
parent e10661be5e
commit e30c647f76
10 changed files with 347 additions and 126 deletions

View File

@ -8,6 +8,12 @@ pyserial = "*"
aiohttp = "*" aiohttp = "*"
typing-extensions = "*" typing-extensions = "*"
websockets = "*" websockets = "*"
aioftp = "*"
colorist = "*"
matplotlib = "*"
bokeh = "*"
bokeh-sampledata = "*"
scipy = "*"
[dev-packages] [dev-packages]

76
a.py
View File

@ -1,10 +1,6 @@
import struct
import serial import serial
import asyncio import asyncio
import time import time
import operator
import functools
import threading
import msp import msp
import async_serial import async_serial
@ -16,12 +12,6 @@ print(time.time())
s = serial.Serial(port='/dev/ttyUSB0', baudrate=250000) s = serial.Serial(port='/dev/ttyUSB0', baudrate=250000)
print(time.time()) print(time.time())
# ccode = bytes((1,))
# s.write(ccode)
while True: while True:
l = s.readline() l = s.readline()
print(l) print(l)
@ -30,40 +20,6 @@ while True:
print('dupa') print('dupa')
# # s.write(make_msp(2))
# # while True:
# # i = s.read()
# # print(f'{i[0]}: ({i})')
pwm = 1000
# def xxx():
# while True:
# code, data = read_msp(s)
# print('aaa')
# print(code)
# print(data.hex())
# esc_voltage, esc_current, esc_power, load_thrust, load_left, rot_e, rot_o, temp0, temp1, temp2, basic_data_flag, acc_x, acc_y, acc_z, vibration, raw_pressure_p, raw_pressure_t, load_right, pro_data_flag = struct.unpack_from('<ffffffffffchhhhhhfc', data)
# # print(esc_voltage)
# # print(esc_current)
# # print(esc_power)
# # print(rot_e)
# print(load_thrust)
# print(load_left)
# print(load_right)
# # print(acc_x)
# # print(acc_y)
# # print(acc_z)
# t = threading.Thread(target=xxx)
# t.start()
# while True:
# s.write(make_poll(pwm))
# time.sleep(0.2)
async def main(): async def main():
r, w = async_serial.wrap_serial(s) r, w = async_serial.wrap_serial(s)
@ -74,18 +30,16 @@ async def main():
await thr.ensure_running() await thr.ensure_running()
await asyncio.sleep(10) await asyncio.sleep(10)
sample = thr.samples[-1] sample = thr.samples_raw[-1]
thrust_tare = raw_thrust(sample.load_thrust) thrust_tare = raw_thrust(sample.load_thrust)
torque_tare = raw_torque(sample.load_left, sample.load_right) torque_tare = raw_torque(sample.load_left, sample.load_right)
while True: while True:
# thr.mot_pwm = 2000 if thr.mot_pwm == 1000:
# await asyncio.sleep(1000) thr.mot_pwm = 1100
thr.mot_pwm += 10 thr.mot_pwm += 10
# print(thr.mot_pwm) await thr.stabilize_rpm(5, 2)
# await asyncio.sleep(1) sample = thr.samples_raw[-1]
await thr.stabilize_rpm(4, 1)
sample = thr.samples[-1]
thrust = raw_thrust(sample.load_thrust) - thrust_tare thrust = raw_thrust(sample.load_thrust) - thrust_tare
torque = raw_torque(sample.load_left, sample.load_right) - torque_tare torque = raw_torque(sample.load_left, sample.load_right) - torque_tare
print(f'{thr.mot_pwm} {sample.rot_e} {thrust} {torque}') print(f'{thr.mot_pwm} {sample.rot_e} {thrust} {torque}')
@ -93,24 +47,4 @@ async def main():
if thr.mot_pwm >= 1900: if thr.mot_pwm >= 1900:
break break
# while True:
# global pwm
# print(pwm)
# res = await m.do_poll(pwm)
# print(res.load_thrust*1000)
# await asyncio.sleep(0.2)
# print(res)
# def worker():
# asyncio.run(main())
# t = threading.Thread(target=worker)
# t.start()
asyncio.run(main()) asyncio.run(main())

51
analysis_test.py Normal file
View File

@ -0,0 +1,51 @@
import pickle
import math
from measurement_station import OpPointData
class MyCustomUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
module = 'measurement_station'
return super().find_class(module, name)
f = open('m3.pickle', 'rb')
data = MyCustomUnpickler(f).load()
d1 = data[1100]
def prrow(r: OpPointData):
print(r.data_thrust_stand_avg.thrust, end=',')
print(r.data_thrust_stand_avg.torque, end=',')
print(r.data_thrust_stand_avg.rot_speed, end=',')
for v in r.data_accustic_avg.values():
print(v, end=',')
print()
print(list(list(data.values())[0].data_accustic_avg.keys()))
for pwm, d in data.items():
prrow(d)
# thrusts = [d.data_thrust_stand_avg.thrust for d in data.values()]
# powers = [d.data_thrust_stand_avg.torque * d.data_thrust_stand_avg.rot_speed * -2*math.pi / 7 for d in data.values()]
# laeqs = [d.data_accustic_avg['S1 LAeq'] for d in data.values()]
# lzeqs = [d.data_accustic_avg['S1 LZeq'] for d in data.values()]
# import matplotlib.pyplot as plt
# _, ax1 = plt.subplots()
# ax1.plot(thrusts, laeqs, label="LAeq [dB]", color='blue')
# ax1.plot(thrusts, lzeqs, label="LZeq [dB]", color='red')
# ax1.set_xlabel("Thrust [N]")
# ax2 = ax1.twinx()
# ax2.plot(thrusts, powers, label="Power [W]", color='green')
# ax1.legend()
# ax2.legend(loc=1)
# plt.title("Powertrain noise measurement")
# plt.savefig('fig.svg')

79
bokehtest.py Normal file
View File

@ -0,0 +1,79 @@
from dataclasses import fields
from typing import Iterable, Sequence
from bokeh.core.enums import SizingMode, SizingModeType
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, MultiChoice, Select
from bokeh.plotting import figure
import pickle
from measurement_station import OpPointData
def oppoint_data_src(p: OpPointData) -> dict[str, float]:
ret = {}
# print(fields(p.data_thrust_stand_avg)[0])
ret.update({k: v for k, v in field_entries(p.data_thrust_stand_avg).items()})
return ret
def field_entries(datacls) -> dict[str, float]:
fnames = [f.name for f in fields(datacls)]
return {n: getattr(datacls, n) for n in fnames}
def read_series(fname: str) -> dict[int, OpPointData]:
with open(fname, 'rb') as f:
return pickle.load(f)
def series_dataclosrc(series: dict[int, OpPointData]) -> ColumnDataSource:
# entries = [field_entries(v) for v in series.values()]
entries = [oppoint_data_src(p) for p in series.values()]
keys = entries[0].keys()
data = { key: [e[key] for e in entries] for key in keys }
return ColumnDataSource(data)
filespeed = 15
files = [ f'./dmuch/gf7035_v{filespeed}.pickle', f'./dmuch/gf7042_v{filespeed}.pickle', f'./dmuch/apc838_v{filespeed}.pickle', f'./dmuch/apc938_v{filespeed}.pickle', f'./dmuch/ep_v{filespeed}.pickle' ]
series = [ read_series(f) for f in files ]
print(oppoint_data_src(series[0][1500]))
print(series_dataclosrc(series[0]))
sources = [series_dataclosrc(s) for s in series]
keys = list(sources[0].data.keys())
p = figure(title="Dynamic Column Plot", x_axis_label='Index', y_axis_label='Value', tools=['hover', 'pan', 'xwheel_zoom'])
p.sizing_mode = 'scale_both' # type: ignore
multi_choice = MultiChoice(title="Choose Columns to Plot", options=keys)
xsel = Select(title="Y axis:", value="cos", options=keys)
colors = ['blue', 'green', 'red', 'yellow', 'orange']
def update_plot(attr, _, new_values):
p.renderers = [] # type: ignore
for source, fname, color in zip(sources, files, colors):
for column in multi_choice.value: # type: ignore
p.line(x=xsel.value, y=column, source=source, legend_label=f'{fname} {column}', line_width=2, color=color)
multi_choice.on_change('value', update_plot)
xsel.on_change('value', update_plot)
layout = column(multi_choice, xsel, p)
layout.sizing_mode = 'scale_both' # type: ignore
curdoc().add_root(layout)

31
main_dm.py Normal file
View File

@ -0,0 +1,31 @@
import asyncio
from measurement_station import ConnectionParams, meas_series
CONN_PARAMS = ConnectionParams(
stand_tty='/dev/ttyUSB0',
nor_addr='10.145.1.1',
nor_ftp_user='AAAA',
nor_ftp_pass='1234',
nor_recordings_dir='/SD Card/NorMeas/Nor14530408/TEST'
)
range(1200, 1500, 50)
async def main():
global x
x = await meas_series(CONN_PARAMS, range(1200, 1800, 100))
# logging.basicConfig(level=logging.INFO)
asyncio.run(main(), debug=True)
import pickle
import sys
with open(sys.argv[1], 'wb') as f:
pickle.dump(x, f)

View File

@ -3,18 +3,26 @@ from collections.abc import Iterable, Sequence
from dataclasses import dataclass from dataclasses import dataclass
from logging import debug from logging import debug
import logging import logging
from typing import Optional
from logger import spr from logger import spr
from norsonic_parser import parse_report
import thrust_stand import thrust_stand
import norsonic import norsonic
from norsonic_fetcher import nor_get_reports, recording_path from norsonic_fetcher import nor_get_reports
from thrust_stand import ThrustStand, ThrustStandMeasurement from thrust_stand import ThrustStand, ThrustStandMeasurement
@dataclass @dataclass(frozen=True)
class OpPointData: class OpPointData:
data_thrust_stand: Sequence[ThrustStandMeasurement] data_thrust_stand: Sequence[ThrustStandMeasurement]
data_accustic: dict raw_nor_report: Optional[bytes]
@property
def data_accustic(self):
if self.raw_nor_report is None:
raise ValueError()
return parse_report(self.raw_nor_report)
@property @property
def data_thrust_stand_avg(self): def data_thrust_stand_avg(self):
@ -24,6 +32,12 @@ class OpPointData:
def data_accustic_avg(self): def data_accustic_avg(self):
return {k: sum(map(float, (row[k] for row in self.data_accustic)))/len(self.data_accustic) for k in self.data_accustic[0].keys() if k != 'Date'} return {k: sum(map(float, (row[k] for row in self.data_accustic)))/len(self.data_accustic) for k in self.data_accustic[0].keys() if k != 'Date'}
import sys
async def ainput(string: str) -> str:
await asyncio.get_event_loop().run_in_executor(
None, lambda s=string: sys.stdout.write(s+' '))
return await asyncio.get_event_loop().run_in_executor(
None, sys.stdin.readline)
@dataclass @dataclass
@ -34,14 +48,6 @@ class ConnectionParams:
nor_ftp_pass: str nor_ftp_pass: str
nor_recordings_dir: str nor_recordings_dir: str
CONN_PARAMS = ConnectionParams(
stand_tty='/dev/ttyUSB0',
nor_addr='10.145.1.1',
nor_ftp_user='AAAA',
nor_ftp_pass='1234',
nor_recordings_dir='/SD Card/NorMeas/Nor14530408/TEST'
)
async def meas_series(params: ConnectionParams, pwms: Iterable[int]): async def meas_series(params: ConnectionParams, pwms: Iterable[int]):
stand = await ThrustStand.open_connection(params.stand_tty) stand = await ThrustStand.open_connection(params.stand_tty)
nor = await norsonic.open_connection(params.nor_addr) nor = await norsonic.open_connection(params.nor_addr)
@ -53,16 +59,31 @@ async def meas_series(params: ConnectionParams, pwms: Iterable[int]):
sample, = stand.get_samples_raw(1) sample, = stand.get_samples_raw(1)
stand.tare_thrust = thrust_stand.raw_thrust(sample.load_thrust) stand.tare_thrust = thrust_stand.raw_thrust(sample.load_thrust)
stand.tare_torque = thrust_stand.raw_torque(sample.load_left, sample.load_right) stand.tare_torque = thrust_stand.raw_torque(sample.load_left, sample.load_right)
stand.tare_current = sample.esc_current
stand.mot_pwm = 1000
####
# stand.mot_pwm = 1200
# await ainput("Press Enter to continue...")
for pwm in pwms: for pwm in pwms:
stand.mot_pwm = pwm stand.mot_pwm = pwm
spr(f'Output: {pwm}PWM') spr(f'Output: {pwm}PWM')
await stand.stabilize_rpm(5, 1) await stand.stabilize_rpm(10, 4)
spr(f'Starting measurement') spr(f'Starting measurement')
stand_series_pending = stand.start_meas_series() stand_series_pending = stand.start_meas_series()
#
files_nor[pwm] = await norsonic.record(nor) files_nor[pwm] = await norsonic.record(nor)
await asyncio.sleep(1)
spr(f'Done') spr(f'Done')
results_stand[pwm] = stand.finish_meas_series(stand_series_pending) results_stand[pwm] = stand.finish_meas_series(stand_series_pending)
print(results_stand[pwm][0])
while stand.mot_pwm > 1000:
stand.mot_pwm -= 10
await asyncio.sleep(0.1)
stand.mot_pwm = 1000 stand.mot_pwm = 1000
@ -77,15 +98,9 @@ async def meas_series(params: ConnectionParams, pwms: Iterable[int]):
ret = { ret = {
pwm: OpPointData( pwm: OpPointData(
data_thrust_stand=results_stand[pwm], data_thrust_stand=results_stand[pwm],
data_accustic=nor_reports[i] # raw_nor_report=None
raw_nor_report=nor_reports[i]
) for i, pwm in enumerate(pwms) ) for i, pwm in enumerate(pwms)
} }
return ret return ret
async def main():
global x
x = await meas_series(CONN_PARAMS, range(1100, 1950, 30))
# logging.basicConfig(level=logging.INFO)
# asyncio.run(main(), debug=True)

27
msp.py
View File

@ -6,10 +6,17 @@ import struct
from typing import Awaitable, Dict, Optional, Self from typing import Awaitable, Dict, Optional, Self
from dataclasses import dataclass from dataclasses import dataclass
import serial
import async_serial
from logger import spr
MSP_MAGIC_OUT = b"$R<" MSP_MAGIC_OUT = b"$R<"
MSP_MAGIC_IN = b"$R>" MSP_MAGIC_IN = b"$R>"
MSP_CODE_POLL = 3 MSP_CODE_POLL = 3
MSP_TTY_DEF_BAUDRATE = 250000
def make_msp(code: int, data: bytes = b'') -> bytes: def make_msp(code: int, data: bytes = b'') -> bytes:
ret = bytearray(MSP_MAGIC_OUT) ret = bytearray(MSP_MAGIC_OUT)
if len(data) > 255: if len(data) > 255:
@ -126,3 +133,23 @@ class MSPSlave:
async def do_poll(self, esc_pwm: int) -> PollResponse: async def do_poll(self, esc_pwm: int) -> PollResponse:
resp = await self.do_request(MSP_CODE_POLL, make_poll(esc_pwm)) resp = await self.do_request(MSP_CODE_POLL, make_poll(esc_pwm))
return PollResponse.from_bytes(resp) return PollResponse.from_bytes(resp)
# TODO
# ASYNC rozjebion
# TODO
@staticmethod
async def open_connection(tty: str, baudrate = MSP_TTY_DEF_BAUDRATE) -> 'MSPSlave':
s = serial.Serial(port=tty, baudrate = baudrate)
while True:
l = s.readline()
print(l)
if l == b'Ready\r\n':
break
spr('MSP: Ready')
r, w = async_serial.wrap_serial(s)
m = MSPSlave(r, w)
await m.ensure_reader()
return m

View File

@ -10,7 +10,6 @@ FNAME = '/SD Card/NorMeas/Nor14530408/TEST/VIP 124 2024-12-10 15-31-19/VIP 124 2
DNAME = '/SD Card/NorMeas/Nor14530408/TEST/VIP 124 2024-12-10 15-31-19/' DNAME = '/SD Card/NorMeas/Nor14530408/TEST/VIP 124 2024-12-10 15-31-19/'
RECORDINGS_PATH = '/SD Card/NorMeas/Nor14530408/TEST' RECORDINGS_PATH = '/SD Card/NorMeas/Nor14530408/TEST'
CRLF = '\r\n' CRLF = '\r\n'
COL_SEPARATOR = '\t'
STR_DIR = '<DIR>' STR_DIR = '<DIR>'
def recording_path(rec_name: str) -> str: def recording_path(rec_name: str) -> str:
@ -40,32 +39,10 @@ async def ftp_fetch(client: aioftp.Client, path: str) -> bytes:
stream.close() stream.close()
return file return file
def parse_report_table(table: str): async def nor_get_reports(addr: str, user: str, password: str, recs: Iterable[str]) -> Sequence[bytes]:
_, head_cols, *rows = table.split(CRLF)
cols = head_cols.split(COL_SEPARATOR)
def parse_row(row: str):
vals = row.split(COL_SEPARATOR)
if len(vals) != len(cols):
print(vals)
print(cols)
print(len(cols))
print(len(vals))
raise ValueError('NorPaeser: Invalid row read')
return {cols[i]: vals[i] for i in range(len(cols))}
return [parse_row(r) for r in rows if len(r) > 0]
def parse_report(report: bytes):
*header, glob, prof = report.decode().split(2*CRLF)
t_prof = parse_report_table(prof)
# t_glob = parse_report_table(glob)
# return t_glob, t_prof
return t_prof
async def nor_get_reports(addr: str, user: str, password: str, recs: Iterable[str]) -> Sequence:
async with aioftp.Client.context(addr, user=user, password=password, parse_list_line_custom=ftp_parse_line) as ftp: async with aioftp.Client.context(addr, user=user, password=password, parse_list_line_custom=ftp_parse_line) as ftp:
return [parse_report(await ftp_fetch(ftp, recording_path(p))) for p in recs] return [await ftp_fetch(ftp, recording_path(p)) for p in recs]
async def main(): async def main():

26
norsonic_parser.py Normal file
View File

@ -0,0 +1,26 @@
CRLF = '\r\n'
COL_SEPARATOR = '\t'
def parse_report_table(table: str):
_, head_cols, *rows = table.split(CRLF)
cols = head_cols.split(COL_SEPARATOR)
def parse_row(row: str):
vals = row.split(COL_SEPARATOR)
if len(vals) != len(cols):
print(vals)
print(cols)
print(len(cols))
print(len(vals))
raise ValueError('NorPaeser: Invalid row read')
return {cols[i]: vals[i] for i in range(len(cols))}
return [parse_row(r) for r in rows if len(r) > 0]
def parse_report(report: bytes):
*header, glob, prof = report.decode().split(2*CRLF)
t_prof = parse_report_table(prof)
# t_glob = parse_report_table(glob)
# return t_glob, t_prof
return t_prof

View File

@ -1,8 +1,13 @@
import asyncio import asyncio
import numbers
import statistics
from asyncio.futures import Future from asyncio.futures import Future
from asyncio.tasks import Task from asyncio.tasks import Task
from collections.abc import Sequence
from dataclasses import dataclass, fields
import operator
from time import time from time import time
from typing import Optional, cast from typing import Any, Callable, Optional, Self, cast
from msp import MSPSlave, PollResponse from msp import MSPSlave, PollResponse
@ -12,6 +17,7 @@ HINGE_DISTANCE = 0.07492
THRUST_CONST = 1000 * 5 / 5 * GRAVITY_CONST THRUST_CONST = 1000 * 5 / 5 * GRAVITY_CONST
TORQUE_CONST = 1000 * 2 / 5 * GRAVITY_CONST * HINGE_DISTANCE / 2 TORQUE_CONST = 1000 * 2 / 5 * GRAVITY_CONST * HINGE_DISTANCE / 2
CAL_POLES = 14
CAL_HINGE_LEFT = 1.2100092475098374 CAL_HINGE_LEFT = 1.2100092475098374
CAL_HINGE_RIGHT = 1.2590952216896254 CAL_HINGE_RIGHT = 1.2590952216896254
CAL_LEFT = 0.9663293361785854 CAL_LEFT = 0.9663293361785854
@ -19,6 +25,7 @@ CAL_RIGHT = -0.9575068323376389
CAL_THRUST = 0.9516456828857573 CAL_THRUST = 0.9516456828857573
CAL_TORQUE_LEFT = CAL_LEFT * CAL_HINGE_LEFT CAL_TORQUE_LEFT = CAL_LEFT * CAL_HINGE_LEFT
CAL_TORQUE_RIGHT = CAL_RIGHT * CAL_HINGE_RIGHT CAL_TORQUE_RIGHT = CAL_RIGHT * CAL_HINGE_RIGHT
CAL_SYNCSPEED = 2/CAL_POLES * 2* 3.1415
def raw_torque(val_left: float, val_right: float) -> float: def raw_torque(val_left: float, val_right: float) -> float:
return ((val_right * CAL_TORQUE_RIGHT) - (val_left * CAL_TORQUE_LEFT)) * TORQUE_CONST return ((val_right * CAL_TORQUE_RIGHT) - (val_left * CAL_TORQUE_LEFT)) * TORQUE_CONST
@ -26,18 +33,58 @@ def raw_torque(val_left: float, val_right: float) -> float:
def raw_thrust(val: float) -> float: def raw_thrust(val: float) -> float:
return val * CAL_THRUST * THRUST_CONST return val * CAL_THRUST * THRUST_CONST
def raw_torque_resp(raw: PollResponse) -> float:
return raw_torque(raw.load_left, raw.load_right)
def raw_thrust_resp(raw: PollResponse) -> float:
return raw_thrust(raw.load_thrust)
@dataclass
class PendingMeasurementSeries:
start_sample_num: int
@dataclass
class ThrustStandMeasurement: class ThrustStandMeasurement:
pass thrust: float
torque: float
rot_speed: float
volt: float
current: float
@classmethod
def num_operation(cls, op: Callable[[numbers.Real, numbers.Real], numbers.Real], a: Self, b: Self | numbers.Real):
results = {}
for f in fields(cls):
val_a: numbers.Real = a if isinstance(a, numbers.Real) else getattr(a, f.name)
val_b: numbers.Real = b if isinstance(b, numbers.Real) else getattr(b, f.name)
results[f.name] = op(val_a, val_b)
return cls(**results)
def __add__(self, other: Self):
return self.num_operation(operator.add, self, other)
def __truediv__(self, divisor):
# def __truediv__(self, divisor):
return self.num_operation(operator.truediv, self, divisor)
def __pow__(self, power):
return self.num_operation(operator.pow, self, power)
@classmethod
def zero(cls) -> Self:
return cls(0, 0, 0, 0, 0)
class ThrustStand: class ThrustStand:
mot_pwm: int mot_pwm: int
msp: MSPSlave msp: MSPSlave
sample_number: int sample_number: int
samples: list[PollResponse] samples_raw: list[PollResponse]
tare_thrust: float tare_thrust: float
tare_torque: float tare_torque: float
tare_current: float
_next_sample_future: Future _next_sample_future: Future
@ -48,7 +95,8 @@ class ThrustStand:
self.msp = msp self.msp = msp
self.mot_pwm = 1000 self.mot_pwm = 1000
self._poller_task = None self._poller_task = None
self.samples = [] self.sample_number = 0
self.samples_raw = []
self._next_sample_future = Future() self._next_sample_future = Future()
@ -63,7 +111,8 @@ class ThrustStand:
async def _do_poll(self): async def _do_poll(self):
res = await self.msp.do_poll(self.mot_pwm) res = await self.msp.do_poll(self.mot_pwm)
self.samples.append(res) self.samples_raw.append(res)
self.sample_number += 1
# print(res.rot_e) # print(res.rot_e)
self._next_sample_future.set_result(None) self._next_sample_future.set_result(None)
self._next_sample_future = Future() self._next_sample_future = Future()
@ -80,16 +129,42 @@ class ThrustStand:
await self.wait_samples(window) await self.wait_samples(window)
while True: while True:
samples = self.samples[-window:] samples = self.samples_raw[-window:]
rpms = list(map(lambda s: s.rot_e, samples)) rpms = list(map(lambda s: s.rot_e, samples))
if max(rpms) - min(rpms) < tolerance: if max(rpms) - min(rpms) < tolerance:
return return
# print(rpms)
await self.next_sample() await self.next_sample()
def next_sample(self) -> Future: def next_sample(self) -> Future:
return asyncio.shield(self._next_sample_future) return asyncio.shield(self._next_sample_future)
def sample_from_raw(self, raw: PollResponse) -> ThrustStandMeasurement:
return ThrustStandMeasurement(
raw_thrust_resp(raw) - self.tare_thrust,
raw_torque_resp(raw) - self.tare_torque, raw.rot_e,
raw.esc_voltage,
raw.esc_current - self.tare_current,
)
def get_samples_raw(self, n: int) -> Sequence[PollResponse]:
return self.samples_raw[-n:]
def get_samples(self, n: int) -> Sequence[ThrustStandMeasurement]:
return list(map(self.sample_from_raw, self.get_samples_raw(n)))
def start_meas_series(self) -> PendingMeasurementSeries:
return PendingMeasurementSeries(self.sample_number)
def finish_meas_series(self, meas: PendingMeasurementSeries) -> Sequence[ThrustStandMeasurement]:
return self.get_samples(self.sample_number - meas.start_sample_num)
@staticmethod
async def open_connection(tty: str):
thr = ThrustStand(await MSPSlave.open_connection(tty))
await thr.ensure_running()
await asyncio.sleep(5)
return thr