Compare commits

...

10 Commits

14 changed files with 382 additions and 185 deletions

View File

@ -10,7 +10,7 @@ insert_final_newline = true
[{*.c,*.h}] [{*.c,*.h}]
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
[*.js] [*.js]
indent_style = space indent_style = space

View File

@ -1 +1,5 @@
* [Ubuntu](https://assets.ubuntu.com/v1/0cef8205-ubuntu-font-family-0.83.zip) font family. ## 🧰 Завиисимости для сборки:
* webui 2.4.2
* Bootstrap 5.3.1
* Семейство шрифтов [Ubuntu](https://assets.ubuntu.com/v1/0cef8205-ubuntu-font-family-0.83.zip)

14
TODO.md Normal file
View File

@ -0,0 +1,14 @@
# 🖱️ Front-end
* [x] Не работает кнопка сброса переключателей зон (98cdbc09df6d51c58125a17985ada3cd07efbcfb)
* [ ] Реализовать проверку пустой команды при нажатии на кнопку `Отправить` команду
* [x] Добавить индикаторы аварийного режима и реверса (7de9849d9aa86955d9c8c902177dc54ff802046e)
* [ ] Вывод информации об устойстве на вкладке `Устройство`
* [ ] Реализовать проверку версии программы из интернета
* [ ] Во вкладку `Прошивка` добавить отображение текущей версии прошивки
* [ ] Не работает кнопка `Записать параметры` во вкладке `Настройка`
* [ ] Не работает кнопка `Прочитать параметры` во вкладке `Настройка`
# ⚙️ Back-end
* [ ] Реализовать получение статуса аварийного режима и реверса real-time для UI

View File

@ -10,11 +10,11 @@ rm brakeconf &> /dev/null
echo "[ 2/$STEPS] Build GUI..." echo "[ 2/$STEPS] Build GUI..."
python3 ./build_gui.py gui/index.html dist/index.html &> /dev/null python3 ./build_gui.py gui/index.html dist/index.html &> /dev/null
xxd -i -n html_document dist/index.html > src/html.h xxd -i -n app_html dist/index.html > src/html.h
# sed -i 's/unsigned char/const unsigned char/g' src/html.h # sed -i 's/unsigned char/const unsigned char/g' src/html.h
echo "[ 3/$STEPS] Compile..." echo "[ 3/$STEPS] Compile..."
gcc -I./webui -o brakeconf src/device.c src/main.c src/ui.c webui/libwebui-2-static.a -lserialport -ljansson gcc -I./webui -o brakeconf src/main.c webui/libwebui-2-static.a -lserialport -ljansson
echo "[ 4/$STEPS] Running..." echo "[ 4/$STEPS] Running..."
./brakeconf icanthink ./brakeconf icanthink

View File

@ -6,99 +6,116 @@ from bs4 import BeautifulSoup, Tag
from jsmin import jsmin from jsmin import jsmin
from csscompressor import compress from csscompressor import compress
# html param IGNORED = 'gui//webui.js'
if __name__ == '__main__':
# html input param
html = sys.argv[1] html = sys.argv[1]
# target param # target output param
target = sys.argv[2] target = sys.argv[2]
# path from html param # path from html param
path = re.sub(r"[^\/]*$", "", html) path = re.sub(r'[^\/]*$', '', html)
# open html file # open html file
print('📂 Open HTML file...', end=' ')
soup = BeautifulSoup(open(html), 'html.parser') soup = BeautifulSoup(open(html), 'html.parser')
print('OK!')
# find last script as anchorpoint # find last script as anchorpoint
lastScript = soup.findAll("script", attrs = {"src" : True})[-1] lastScript = soup.findAll('script', attrs={'src': True})[-1]
# get all scripts containing src attribute (= external scripts) # get all scripts containing src attribute (= external scripts)
scripts = soup.findAll("script", attrs = {"src" : True}) scripts = soup.findAll('script', attrs={'src': True})
# find last style link as anchorpoint # find last style link as anchorpoint
lastStylesheet = soup.findAll("link", attrs = {"rel" : "stylesheet"})[-1] lastStylesheet = soup.findAll('link', attrs={'rel': 'stylesheet'})[-1]
# get all links to css stylesheets # get all links to css stylesheets
stylesheets = soup.findAll("link", attrs = {"rel" : "stylesheet"}) stylesheets = soup.findAll('link', attrs={'rel': 'stylesheet'})
# create list of script srcs # create list of script srcs
print("\nRead Scripts:") print('🔎 Create list of scripts...', end=' ')
scriptsSrc = deque() scriptsSrc = deque()
for script in scripts: for script in scripts:
scriptsSrc.append(path + script.attrs["src"]) scriptsSrc.append(path + script.attrs['src'])
print("\t" + path + script.attrs["src"]) print('Complete!')
# create list of stylesheets srcs # create list of stylesheets srcs
print("\nRead Stylesheets:") print('🔎 Create list of stylesheets...', end=' ')
stylesheetsSrc = deque() stylesheetsSrc = deque()
for stylesheet in stylesheets: for stylesheet in stylesheets:
stylesheetsSrc.append(path + stylesheet.attrs["href"]) stylesheetsSrc.append(path + stylesheet.attrs['href'])
print("\t" + path + stylesheet.attrs["href"]) print('Complete!')
# merge scripts to temp.js # merge scripts to .temp.js
print("\nMerge Scripts:") print('📥 Merge scripts...', end=' ')
print("\t", end="") with open('.temp.js', 'w') as outfileScript:
with open("temp.js", "w") as outfileScript:
for fname in scriptsSrc: for fname in scriptsSrc:
# add space every script # add space every script
if fname != 'gui//webui.js': if fname not in IGNORED:
outfileScript.write("\n") outfileScript.write('\n')
print("~", end="")
with open(fname) as infile: with open(fname) as infile:
for line in infile: for line in infile:
outfileScript.write(line) outfileScript.write(line)
print("\n"); print('Complete!')
print('📥 Merge stylsheets...', end=' ')
# merge stylsheets to temp.css # merge stylsheets to temp.css
print("Merge Stylesheets:") with open('.temp.css', 'w') as outfileCSS:
print("\t", end="")
with open("temp.css", "w") as outfileCSS:
for fname in stylesheetsSrc: for fname in stylesheetsSrc:
# add space every script # add space every script
outfileCSS.write("\n") outfileCSS.write('\n')
print("~", end="")
with open(fname) as infile: with open(fname) as infile:
for line in infile: for line in infile:
outfileCSS.write(line) outfileCSS.write(line)
print("\n"); print('Complete!')
# minify javascript # minify javascript
print("Minify temp.js\n\t~") print('🗃️ Minify scripts...', end=' ')
with open("temp.js") as js: with open('.temp.js') as js:
minified_js = jsmin(js.read()) minified_js = jsmin(js.read())
print('Complete!')
# minify css # minify css
print("\nMinify temp.css\n\t~") print('🗃️ Minify stylsheets...', end=' ')
with open("temp.css") as css: with open('.temp.css') as css:
minified_css = compress(css.read()) minified_css = compress(css.read())
print('Complete!')
# replace scripts with merged and min embed script / css # replace scripts with merged and min embed script / css
print("\nReplacing and deleting\n\t~") print('🔄 Embedding script and stylsheets...', end=' ')
tag = soup.new_tag("script") tag = soup.new_tag('script')
tag["type"] = "text/javascript" tag['type'] = 'text/javascript'
tag.append(minified_js) tag.append(minified_js)
lastScript.replace_with(tag) lastScript.replace_with(tag)
tag = soup.new_tag("style") webui_tag = soup.new_tag('script')
tag["type"] = "text/css" webui_tag['type'] = 'text/javascript'
webui_tag['src'] = '/webui.js'
tag.insert_before(webui_tag)
tag = soup.new_tag('style')
tag['type'] = 'text/css'
tag.append(minified_css) tag.append(minified_css)
lastStylesheet.replace_with(tag) lastStylesheet.replace_with(tag)
print('Complete!')
# remove script and style tags # remove script and style tags
print('🧹 Cleaning...', end=' ')
for script in scripts: for script in scripts:
script.decompose() script.decompose()
for stylesheet in stylesheets: for stylesheet in stylesheets:
stylesheet.decompose() stylesheet.decompose()
#remove temp # remove temp files
os.remove("temp.js") os.remove('.temp.js')
os.remove("temp.css") os.remove('.temp.css')
print('Complete!')
# save html as target # save html as target
file = open(target,"w") print('💾 Save builded document...', end=' ')
file = open(target, 'w')
file.write(soup.prettify()) file.write(soup.prettify())
file.close() file.close()
print('Complete!', end='\n\n')
print("\nFIN\n") print('🏁 Complete')

View File

@ -41,6 +41,8 @@ window.onload = function () {
document.getElementById('btn_app_close').addEventListener('click', close_app); document.getElementById('btn_app_close').addEventListener('click', close_app);
document.getElementById('btn_port_refresh').addEventListener('click', refresh_ports); document.getElementById('btn_port_refresh').addEventListener('click', refresh_ports);
document.getElementById('btn_device_connect').addEventListener('click', connect_to_device); document.getElementById('btn_device_connect').addEventListener('click', connect_to_device);
document.getElementById('option_zone_reset').addEventListener('click', reset_test_zones);
}; };
/** /**
@ -125,7 +127,7 @@ function refresh_ports() {
} }
/** /**
* Подключается к устройству * Подключается к выбранному устройству
*/ */
function connect_to_device() { function connect_to_device() {
const port_selector = document.getElementById('port_selector'); const port_selector = document.getElementById('port_selector');
@ -137,7 +139,9 @@ function connect_to_device() {
return; return;
} }
webui.call('webui_connect_device', port_selector.value); webui.call('webui_connect_device', port_selector.value).then((response) => {
alert(`!: ${response}`);
});
if (btn_device_connect.innerText == 'Подключиться') { if (btn_device_connect.innerText == 'Подключиться') {
port_selector.disabled = true; port_selector.disabled = true;
@ -170,6 +174,35 @@ function add_line_log(text) {
} }
} }
/**
* Меняет состояние checkbox'а статуса режимов в окне `Тестирование`
*/
function set_mode_status(mode, status) {
const set_value = (element, status) => (element.checked = status);
if (mode == 'emergency') {
const element = document.getElementById('mode_status_emergency');
status ? set_value(element, status) : set_value(element, status);
} else if (mode == 'reverse') {
const element = document.getElementById('mode_status_reverse');
status ? set_value(element, status) : set_value(element, status);
} else {
return -1;
}
return 0;
}
/**
* Сбрасывает состояние переключателей зон в окне `Тестирование`
*/
function reset_test_zones() {
const option_zones = document.getElementsByName('option-zone');
for (let checkbox of option_zones) {
checkbox.checked = false;
}
}
/** /**
* *
*/ */

View File

@ -7,8 +7,8 @@
<link rel="stylesheet" type="text/css" href="bootstrap-v5.3.1.min.css" /> <link rel="stylesheet" type="text/css" href="bootstrap-v5.3.1.min.css" />
<link rel="stylesheet" type="text/css" href="ubuntu-font.css" /> <link rel="stylesheet" type="text/css" href="ubuntu-font.css" />
<link rel="stylesheet" type="text/css" href="styles.css" /> <link rel="stylesheet" type="text/css" href="styles.css" />
<script src="app.js"></script>
<script src="/webui.js"></script> <script src="/webui.js"></script>
<script src="/app.js"></script>
</head> </head>
<body class="d-flex h-100 text-bg-dark"> <body class="d-flex h-100 text-bg-dark">
<div class="d-flex h-100 mx-auto p-3"> <div class="d-flex h-100 mx-auto p-3">
@ -181,7 +181,7 @@
</button> </button>
</div> </div>
<hr /> <hr />
<p class="text-center m-0">Версия: 1.0.0</p> <p class="text-center m-0">Версия: 0.9.9</p>
</nav> </nav>
<main class="d-flex flex-fill" style="width: 600px"> <main class="d-flex flex-fill" style="width: 600px">
<!-- Device --> <!-- Device -->
@ -205,6 +205,44 @@
<!-- Testing --> <!-- Testing -->
<div id="appWindowTesting" class="d-none flex-fill card shadow p-3"> <div id="appWindowTesting" class="d-none flex-fill card shadow p-3">
<!-- Статус режимов -->
<!--
Отображает в реальном времени состоянии активации
аварийного режима и реверса
-->
<p class="text-center">Статус режимов</p>
<div class="mb-3">
<div class="input-group">
<div class="input-group-text rounded-bottom-0">
<input
id="mode_status_emergency"
class="form-check-input mt-0"
type="checkbox"
onclick="return false;" />
</div>
<input
type="text"
class="form-control rounded-bottom-0"
value="Аварийный режим"
readonly />
</div>
<div class="input-group">
<div class="input-group-text border-top-0 rounded-top-0">
<input
id="mode_status_reverse"
class="form-check-input mt-0"
type="checkbox"
onclick="return false;" />
</div>
<input
type="text"
class="form-control border-top-0 rounded-top-0"
value="Реверс"
readonly />
</div>
</div>
<!-- Активация аварийного режима и реверса -->
<p class="text-center">Активация режимов</p> <p class="text-center">Активация режимов</p>
<div class="d-flex btn-group mb-3"> <div class="d-flex btn-group mb-3">
<input <input
@ -267,9 +305,77 @@
</div> </div>
</div> </div>
<!-- Settings --> <!-- Окно `Настройка` -->
<div id="appWindowSettings" class="d-none flex-fill card shadow p-3"> <div id="appWindowSettings" class="d-none flex-fill card shadow p-3">
<div class="alert alert-danger text-center">В разработке</div> <div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="setting_buzzer"
checked />
<label class="form-check-label" for="setting_buzzer">Включить зуммер</label>
</div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="setting_zones_by_analog"
checked />
<label class="form-check-label" for="setting_zones_by_analog">
Активация зоны аналоговым методом
</label>
</div>
<hr />
<p class="text-center">Настройка времени работы штока актуатора</p>
<div class="row mb-1">
<label for="setting_rod_full_push" class="col-6 col-form-label">
Полное освобождение педали
</label>
<div class="col-6">
<input
type="number"
class="form-control"
id="setting_rod_full_push"
min="200"
max="4000" />
</div>
</div>
<div class="row mb-1">
<label for="setting_rod_pre_brake" class="col-6 col-form-label">
Предварительное торможение
</label>
<div class="col-6">
<input
type="number"
class="form-control"
id="setting_rod_pre_brake"
min="200"
max="4000" />
</div>
</div>
<div class="row mb-1">
<label for="setting_rod_full_brake" class="col-6 col-form-label">
Полное торможение
</label>
<div class="col-6">
<input
type="number"
class="form-control"
id="setting_rod_full_brake"
min="200"
max="4000" />
</div>
</div>
<hr />
<div class="d-grid gap-2">
<button class="btn btn-primary">Записать параметры</button>
<button class="btn btn-outline-success">Прочитать параметры</button>
</div>
</div> </div>
<!-- FirmwareUpdate --> <!-- FirmwareUpdate -->
@ -304,13 +410,11 @@
<p class="text-center">О программе</p> <p class="text-center">О программе</p>
<div class="alert alert-info text-center"> <div class="alert alert-info text-center">
Программа для настройки Программа для настройки
<span class="fw-bold">модуля торможения</span> <span class="fw-bold">модуля торможения</span>.
</div> <br />
<div class="alert alert-warning text-center">
Разработано специально для Разработано специально для
<span class="fw-bold">НК-Сервис</span> <span class="fw-bold">НК-Сервис</span>
</div> </div>
<div class="alert alert-dark text-center">2023 by Alexander Popov</div>
</div> </div>
</main> </main>
</div> </div>

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[tool.black]
skip-string-normalization = true
pycodestyle = true
line-length = 100

View File

@ -8,4 +8,7 @@
#define DEBUG #define DEBUG
#include "html.h"
struct sp_port *serial_port;
#endif #endif

View File

@ -1,34 +0,0 @@
/*
* AUTHOR: Alexander Popov <iiiypuk {at} fastmail.fm>
* DESC: ...
*/
#include "device.h"
json_t *get_serial_ports() {
struct sp_port **port_list;
enum sp_return result = sp_list_ports(&port_list);
json_t *ports_data = NULL;
json_t *ports_array = NULL;
ports_array = json_array();
if (result == SP_OK) {
/* Get the name of the port. */
int i;
for (i = 0; port_list[i] != NULL; i++) {
struct sp_port *port = port_list[i];
char *port_name = sp_get_port_name(port);
json_array_append(ports_array, json_string(port_name));
}
sp_free_port_list(port_list);
}
ports_data = json_object();
json_object_set_new(ports_data, "ports", ports_array);
return ports_data;
}

View File

@ -6,9 +6,73 @@
#ifndef DEVICE_H_ #ifndef DEVICE_H_
#define DEVICE_H_ #define DEVICE_H_
#include <libserialport.h> int check(enum sp_return result) {
#include <jansson.h> char *error_message;
json_t *get_serial_ports(); switch (result) {
case SP_ERR_ARG:
puts("Error: Invalid argument.");
abort();
case SP_ERR_FAIL:
error_message = sp_last_error_message();
printf("Error: Failed: %s\n", error_message);
sp_free_error_message(error_message);
abort();
case SP_ERR_SUPP:
puts("Error: Not supported.");
abort();
case SP_ERR_MEM:
puts("Error: Couldn't allocate memory.");
abort();
case SP_OK:
default:
return result;
}
}
json_t *device_get_available() {
struct sp_port **port_list;
enum sp_return result = sp_list_ports(&port_list);
json_t *ports_data = NULL;
json_t *ports_array = NULL;
ports_array = json_array();
if (result == SP_OK) {
/* Get the name of the port. */
int i;
for (i = 0; port_list[i] != NULL; i++) {
struct sp_port *port = port_list[i];
char *port_name = sp_get_port_name(port);
json_array_append(ports_array, json_string(port_name));
}
sp_free_port_list(port_list);
}
ports_data = json_object();
json_object_set_new(ports_data, "ports", ports_array);
return ports_data;
}
int device_connect(const char *port_name) {
check(sp_get_port_by_name(port_name, &serial_port));
check(sp_open(serial_port, SP_MODE_READ_WRITE));
check(sp_set_baudrate(serial_port, 9600));
check(sp_set_bits(serial_port, 8));
check(sp_set_parity(serial_port, SP_PARITY_NONE));
check(sp_set_stopbits(serial_port, 1));
check(sp_set_flowcontrol(serial_port, SP_FLOWCONTROL_NONE));
return 0;
}
int device_disconnect() {
sp_close(serial_port);
}
#endif #endif

View File

@ -1,31 +1,30 @@
#include <stdio.h> #include <stdio.h>
#include <stdbool.h>
#include <string.h> #include <string.h>
#include <libserialport.h>
#include <jansson.h>
#include "webui.h" #include "webui.h"
#include "config.h" #include "config.h"
#include "html.h"
#include "ui.h"
#include "device.h" #include "device.h"
#include "ui.h"
struct sp_port *serial_port;
int main(int argc, char const *argv[]) { int main(int argc, char const *argv[]) {
int app_window = webui_new_window(); int app_window = webui_new_window();
html_document[html_document_len] = '\0'; app_html[app_html_len] = '\0';
#ifndef DEBUG #ifndef DEBUG
webui_set_kiosk(app_window, true); webui_set_kiosk(app_window, true);
#endif #endif
webui_bind(app_window, "close_app", close_app); webui_bind(app_window, "close_app", app_close);
webui_bind(app_window, "webui_refresh_ports", refresh_devices); webui_bind(app_window, "webui_refresh_ports", app_refresh_devices);
webui_bind(app_window, "webui_connect_device", connect_device); webui_bind(app_window, "webui_connect_device", app_connect_device);
printf("Enjoy :)\n"); printf("Enjoy :)\n");
webui_show(app_window, html_document); webui_show(app_window, app_html);
webui_wait(); webui_wait();
return 0; return 0;

View File

@ -1,28 +0,0 @@
/*
* AUTHOR: Alexander Popov <iiiypuk {at} fastmail.fm>
* DESC: ...
*/
#include "ui.h"
void close_app(webui_event_t* e) {
printf("Bye!\n");
// webui_destroy();
webui_exit();
}
void refresh_devices(webui_event_t* e) {
const char *available_ports = json_dumps(get_serial_ports(), 0);
#ifdef DEBUG
printf("Доступные порты:\n%s\n", available_ports);
#endif
webui_return_string(e, available_ports);
}
void connect_device(webui_event_t* e) {
const char* str = webui_get_string(e);
webui_return_bool(e, true);
}

View File

@ -6,16 +6,33 @@
#ifndef UI_H_ #ifndef UI_H_
#define UI_H_ #define UI_H_
#include <stdio.h> void app_close(webui_event_t* e) {
#include <libserialport.h> printf("Bye!\n");
#include <jansson.h> webui_exit();
#include "webui.h" }
#include "config.h" void app_refresh_devices(webui_event_t* e) {
#include "device.h" const char *available_ports = json_dumps(device_get_available(), 0);
void close_app(webui_event_t* e); #ifdef DEBUG
void connect_device(webui_event_t* e); printf("Доступные порты:\n%s\n", available_ports);
void refresh_devices(webui_event_t* e); #endif
webui_return_string(e, available_ports);
}
void app_connect_device(webui_event_t* e) {
const char* port = webui_get_string(e);
int result;
#ifdef DEBUG
printf("Подключение к порту: %s\n", port);
#endif
// TODO: Необходимо проверить подключение
result = device_connect(port);
webui_return_bool(e, (bool)result);
}
#endif #endif