Skip to content

Commit

Permalink
add webserver example, black+isort format all
Browse files Browse the repository at this point in the history
  • Loading branch information
cdump committed Jun 10, 2021
1 parent c473cc3 commit 67ebcb7
Show file tree
Hide file tree
Showing 13 changed files with 629 additions and 58 deletions.
5 changes: 1 addition & 4 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
[isort]
line_length = 79

[flake8]
max-line-length = 200
max-line-length = 130

max-imports = 16

Expand Down
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
## RadiaCode
Библиотека для работы с дозиметром [RadiaCode-101](https://scan-electronics.com/dosimeters/radiacode/radiacode-101), находится в разработке - API не стабилен и возможны изменения.

Библиотека для работы с дозиметром [RadiaCode-101](https://scan-electronics.com/dosimeters/radiacode/radiacode-101)
Пример использования ([backend](radiacode-examples/webserver.py), [frontend](radiacode-examples/webserver.html)):
![radiacode-webserver-example](./screenshot.png)

Находится в разработке, доступны базовые команды: получение спектра, управление звуком и выбро, получение истории измерений и т.п.
### Установка & запуск примера
```
# установка вместе с зависимостями для примеров, уберите [examples] если они вам не нужны
$ pip3 install 'radiacode[examples]' --upgrade
# Запуск вебсервера из скриншота выше
# bluetooth: замените на адрес вашего устройства
$ python3 -m radiacode-examples.webserver --bluetooth-mac 52:43:01:02:03:04
API пока не стабилен и возможны большие изменения!
# или то же самое, но по usb
$ sudo python3 -m radiacode-examples.webserver
![screenshot](./screenshot.png)
# или простой пример с выводом информации в терминал, опции аналогичны webserver
$ python3 -m radiacode-examples.basic
```

### Как запустить
### Разработка
- Установить [python poetry](https://python-poetry.org/docs/#installation)
- Склонировать репозиторий, установить и запустить:
```
$ git clone https://github.com/cdump/radiacode.git
$ cd radiacode
$ poetry install
$ poetry run python3 example.py --bluetooth-mac 52:43:01:02:03:04
$ poetry run python3 radiacode-examples/basic.py --bluetooth-mac 52:43:01:02:03:04 # или без --bluetooth-mac для USB подключения
```
Без указания `--bluetooth-mac` будет использоваться USB подключение.
351 changes: 347 additions & 4 deletions poetry.lock

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "radiacode"
version = "0.1.0"
version = "0.1.1"
description = "Library for RadiaCode-101"
authors = ["Maxim Andreev <[email protected]>"]
license = "MIT"
Expand All @@ -11,18 +11,32 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
]
include = ["radiacode-examples/*"]

[tool.poetry.dependencies]
python = "^3.6.1 || ^3.7"
python = "^3.6.2 || ^3.7"
bluepy = "^1.3.0"
pyusb = "^1.1.1"
aiohttp = {version = "^3.7.4", optional = true}

[tool.poetry.dev-dependencies]
wemake-python-styleguide = "^0.15.2"
jedi = "^0.18.0"
mypy = "^0.812"
pytest = "^6.2.4"
black = "^21.5b2"

[tool.poetry.extras]
examples = ["aiohttp"]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 130
target-version = ["py36"]
skip-string-normalization = true

[tool.isort]
line_length = 130
1 change: 0 additions & 1 deletion example.py → radiacode-examples/basic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import argparse
import sys
import time

from radiacode import RadiaCode
Expand Down
97 changes: 97 additions & 0 deletions radiacode-examples/webserver.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>RadiaCode demo</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-apexcharts"></script>
</head>
<style>
#app > div {
margin: 5px auto;
width: 80%;
text-align: center;
padding: 5px;
border: 1px #aaa dashed;
}
</style>
<body>
<div id="app">
<div>
<apexchart type="bar" height="350" :options="spectrumChartOptions" :series="spectrum_series"></apexchart>
<button @click="updateSpectrum">Update spectrum</button>
</div>
<div>
<apexchart type="line" height="350" :options="ratesChartOptions" :series="rates_series"></apexchart>
<button @click="rates_autoupdate = !rates_autoupdate">Rates autoupdate: {{ rates_autoupdate ? "ON" : "OFF" }}</button>
</div>
</div>

<script>
const common_options = {
chart: {
animations: {enabled: false},
zoom: {autoScaleYaxis: true},
},
dataLabels: {enabled: false},
};
var app = new Vue({
el: '#app',
components: {
apexchart: VueApexCharts,
},
data: function() {
return {
ws: null,
spectrum_duration: 0,
rates_autoupdate: true,
rates_series: [],
spectrum_series: [],
ratesChartOptions: {
...common_options,
title: {text: 'CountRate & DoseRate'},
xaxis: {type: 'datetime'},
yaxis: [
{seriesName: 'countrate', decimalsInFloat: 2, title: {text: 'CPS'}, labels: {formatter:(v) => v.toFixed(4) + ' CPS'}},
{seriesName: 'doserate', decimalsInFloat: 2, title: {text: 'μSv/h'}, labels: {formatter:(v) => v.toFixed(4) + ' μSv/h'}, opposite: true},
],
},
};
},
computed: {
spectrumChartOptions() {
return{
...common_options,
title: {text: `Spectrum, ${this.spectrum_duration} seconds`},
xaxis: {type: 'numeric', title: {text: 'keV'}},
// yaxis: {logarithmic: true},
};
},
},
created() {
this.ws = new WebSocket('ws://' + window.location.host + '/ws')
this.ws.onmessage = this.onmessage;
this.updateSpectrum();
},
beforeDestroy: function() {
this.ws.close();
},
methods: {
onmessage(ev) {
if (!this.rates_autoupdate) {
return;
}
const d = JSON.parse(ev.data);
this.rates_series = d.series;
},
updateSpectrum() {
fetch('/spectrum')
.then(response => response.json())
.then(data => (this.spectrum_duration=data.duration, this.spectrum_series=data.series));
},
},
});
</script>
</body>
</html>
99 changes: 99 additions & 0 deletions radiacode-examples/webserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import argparse
import asyncio
import json
import pathlib

from aiohttp import web

from radiacode import CountRate, DoseRate, RadiaCode, spectrum_channel_to_energy


async def handle_index(request):
return web.FileResponse(pathlib.Path(__file__).parent.absolute() / 'webserver.html')


async def handle_ws(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
request.app.ws_clients.append(ws)
async for _ in ws: # noqa: WPS328
pass
request.app.ws_clients.remove(ws)
return ws


async def handle_spectrum(request):
spectrum = request.app.rc_conn.spectrum()
spectrum_data = [
(int(spectrum_channel_to_energy(channel, spectrum.a0, spectrum.a1, spectrum.a2)), cnt)
for channel, cnt in enumerate(spectrum.counts)
]
print('Spectrum updated')
return web.json_response(
{
'duration': spectrum.duration.total_seconds(),
'series': [{'name': 'spectrum', 'data': spectrum_data}],
},
)


async def process(app):
max_history_size = 128
countrate_history, doserate_history = [], []
while True: # noqa: WPS457
databuf = app.rc_conn.data_buf()
for v in databuf:
if isinstance(v, CountRate):
countrate_history.append(v)
elif isinstance(v, DoseRate):
doserate_history.append(v)

countrate_history.sort(key=lambda x: x.dt)
countrate_history = countrate_history[-max_history_size:]
doserate_history.sort(key=lambda x: x.dt)
doserate_history = doserate_history[-max_history_size:]
jdata = json.dumps(
{
'series': [
{
'name': 'countrate',
'data': [(int(1000 * x.dt.timestamp()), x.count_rate) for x in countrate_history],
},
{
'name': 'doserate',
'data': [(int(1000 * x.dt.timestamp()), 10000 * x.dose_rate) for x in doserate_history],
},
],
},
)
print(f'Rates updated, sending to {len(app.ws_clients)} connected clients')
await asyncio.gather(*[ws.send_str(jdata) for ws in app.ws_clients], asyncio.sleep(1.0))


async def on_startup(app):
asyncio.create_task(process(app))


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--bluetooth-mac', type=str, required=False, help='bluetooth MAC address of radiascan device')
args = parser.parse_args()

app = web.Application()
app.ws_clients = []
if args.bluetooth_mac:
print('will use Bluetooth connection')
app.rc_conn = RadiaCode(bluetooth_mac=args.bluetooth_mac)
else:
print('will use USB connection')
app.rc_conn = RadiaCode()

app.on_startup.append(on_startup)
app.add_routes(
[
web.get('/', handle_index),
web.get('/spectrum', handle_spectrum),
web.get('/ws', handle_ws),
],
)
web.run_app(app)
2 changes: 1 addition & 1 deletion radiacode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from radiacode.bytes_buffer import BytesBuffer
from radiacode.radiacode import RadiaCode
from radiacode.radiacode import spectrum_channel_to_energy, RadiaCode
from radiacode.types import *
2 changes: 1 addition & 1 deletion radiacode/bytes_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def size(self):
return len(self._data) - self._pos

def data(self):
return self._data[self._pos:]
return self._data[self._pos :]

def unpack(self, fmt):
sz = struct.calcsize(fmt)
Expand Down
Loading

0 comments on commit 67ebcb7

Please sign in to comment.