Дисклеймер
Здравствуйте, пишу первый раз, поэтому прошу прощения, если не там разместил или неправильно оформил.
Описание проекта
Ардуино. Есть математическая модель емкости, реализуемая на ардуино. Моделируется отдельно процесс налива и слива жидкости из емкости. Все функции скетча ардуино сделаны отдельными функциями для возможности "пересобрать" скетч при необходимости.
Python. Программа на питоне управляет симуляцией на ардуино, общаясь через последовательный порт. Также происходит индикация параметров процесса, получаемых от ардуино.
Информационные потоки: ардуино - отправляет "0,0" в ответ на аналогичный входной текст (служит для подтверждения подключения и успешного обмена); постоянно отправляет данные о параметрах процесса. (на данный момент также повторяет входные сигналы, для отладки). Python - отправляет управляющие сигналы по нажатиям на кнопки.
Кодировка парсера: key + ',' + data + ';'.
data внутри себя тоже может быть разделена запятой для последующего извлечения составляющих.
key 0: код для подключения и отключения. Если питон передает data = "0", происходит запрос подтверждения подключения. Если питон передает data = "1", он сообщает о намерении отключиться от ардуино, запускает на ней процесс перезагрузки (через сторожевого пса). Ардуино отправляет "0,0" как знак успешного получения "0,0" от питона.
key 1: код для управления режимом симуляции и передачи параметров. Питон отправляет два числа: начальный уровень емкости (флоат) и режим (0 или 1). Режимы: 1 - налив, 0 - слив жидкости из емкости. Ардуино просто присваивает полученные значения в переменные. Ардуино по данному коду отправляет некоторые параметры: h, F1, F2.
К слову режим вроде меняется, так что похоже работает.
key 2: Код для установки параметров емкости и передачи параметров с ардуино. Python отправляет параметры емкости, введенные пользователем. Ардуино принимает данные параметры, присваивает переменные. Также Ардуино отправляет значения степеней открытия клапанов.
key 3: дескриптор для старта и остановки симуляции. Осуществляется нажатием одной кнопки (кнопка с фиксацией). Для старта Питон отправляет data = '1', для остановки '0'.
Проблема
Не работает должным образом обмен данными. Например, не получается переключить режим работы. Не всегда получается перезагрузить ардуино при подключении.
Также, когда поставил обратную отправку получаемых данных на ардуино заметил, что обратная отправка данных осуществляется только при поступлении новых (код нажатия кнопки возвращается на комп только при нажатии следующей).
Текущий вариант кода не изначальный, к сожалению, я частично исковеркал его попытками все починить.
Ниже привожу код полностью и интерфейс программы на питоне.
Слезно прошу помощи, уже отчаялся
Здравствуйте, пишу первый раз, поэтому прошу прощения, если не там разместил или неправильно оформил.
Описание проекта
Ардуино. Есть математическая модель емкости, реализуемая на ардуино. Моделируется отдельно процесс налива и слива жидкости из емкости. Все функции скетча ардуино сделаны отдельными функциями для возможности "пересобрать" скетч при необходимости.
Python. Программа на питоне управляет симуляцией на ардуино, общаясь через последовательный порт. Также происходит индикация параметров процесса, получаемых от ардуино.
Информационные потоки: ардуино - отправляет "0,0" в ответ на аналогичный входной текст (служит для подтверждения подключения и успешного обмена); постоянно отправляет данные о параметрах процесса. (на данный момент также повторяет входные сигналы, для отладки). Python - отправляет управляющие сигналы по нажатиям на кнопки.
Кодировка парсера: key + ',' + data + ';'.
data внутри себя тоже может быть разделена запятой для последующего извлечения составляющих.
key 0: код для подключения и отключения. Если питон передает data = "0", происходит запрос подтверждения подключения. Если питон передает data = "1", он сообщает о намерении отключиться от ардуино, запускает на ней процесс перезагрузки (через сторожевого пса). Ардуино отправляет "0,0" как знак успешного получения "0,0" от питона.
Участок кода Python с key = 0:
def connect(): #нажатие на кнопку "connect"
if len(QSerialPortInfo.availablePorts()) != 0: #если есть доступные порты
serial.setPortName(ui.comL.currentText()) #выбор имени порта в соответствии с выбранным в списке
serial.open(QIODevice.ReadWrite) #открытие соединения
serial.setDataTerminalReady(False)
serialSend(0, 0) # отправка на ардуино
Участок кода с key = 0 на ардуино:
char str [15]; //создание символьного массива из 15 элементов
int amount = Serial.readBytesUntil(';', str, 15); //запись входных данных в массив
Serial.flush();
str[amount] = NULL; //удаление символа конца строки (?)
Serial.println(str);
GParser data(str, ','); //создание объекта GParser из массива str с символом разделителем ','
data.split(); //разделение полученного текста на подстроки, разделенные ','
switch (data.getInt(0)) { //выбор дальнейших действий в зависимости от значения первого полученного символа (дескриптора)
case 0:
if (data.getInt(1) == 0){
Serial.println("0,0");
} else if (data.getInt(1) == 1){
wdt_enable(WDTO_1S);
Участок кода с key = 1 Python:
def save():
global data
if serial.isOpen(): # если есть соединение
try: # конструкция для отлова ошибок
data = str(float(ui.h0.text().replace(",", "."))) # перевод текста из поля ввода в число
if ui.rezhim1.isChecked():
data = data + ",1"
elif ui.rezhim0.isChecked():
data = data + ",0"
else:
error.setText("Выберите режим!") # вывести окно ошибки с текстом "Выберите режим!"
error.exec()
serialSend(1, data) # отправка на ардуино
Участок кода с key = 1 Arduino:
case 1: //если он равен нулю
rezhim = data.getInt(1); //ПРОВЕРИТЬ РАБОТАЕТ ЛИ ТАК
h = data.getInt(2)/1000;
break;
Участок кода с key = 1 Arduino (передача):
void print_inf(){
Serial.print(1); //отправка дескриптора на компьютер
Serial.print(",");
if (isnan(h)||h<0){ h = prev_h;} else { prev_h = h;} //конструкция, необходимая для обхода ошибки (в новых версиях Arduino IDE число не может быть равно 0, вместо этого выводится Nan
Serial.print(h*1000); //отправка текущего значения уровня на компьютер
Serial.print(",");
if (isnan(F1)){ F2 = prev_F1;} else { prev_F1 = F1;} //то же аналогично для расхода жидкости на входе и на выходе
Serial.print(F1,4);
Serial.print(",");
if (isnan(F2)){ F2 = prev_F2;} else { prev_F2 = F2;}
Serial.print(F2,4);
Serial.println(";");
Участок кода с key = 2 Python:
def save_param():
global data
if serial.isOpen(): # если есть соединение
try: # конструкция для отлова ошибок
data = str(float(ui.de.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.he.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.hd1.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.hd2.text().replace(",", "."))) # перевод текста из поля ввода в число
serialSend(2, data) # отправка на ардуино
Участок кода с key = 2 Arduino (прием):
case 2: //если он равен 1
de = data.getInt(1)/1000; //запись диаметра емкости
he = data.getInt(2)/1000; //запись высоты емкости
hd1 = data.getInt(3)/1000; //запись высоты входного отверстия
hd2 = data.getInt(4)/1000; //запись высоты выходного отверстия
break;
Участок кода с key = 2 Ардуино (передача):
Serial.print(2); //отправка дескриптора на компьютер
Serial.print(",");
if (isnan(mim_st)){ mim_st = prev_mim_st;} else { prev_mim_st = mim_st;}
Serial.print(mim_st*100,0);
Serial.print(",");
if (isnan(meo_st)){ meo_st = prev_meo_st;} else { prev_meo_st = meo_st;}
Serial.print(meo_st*100,0);
Serial.println(";");
Участок кода с key = 3 Python:
def start():
global data
if serial.isOpen(): # если есть соединение
serialSend(3, int(ui.start.isChecked())) # отправка на ардуино
Участок кода с key = 3 Ардуино:
case 3: //если он равен нулю
start = data.getInt(1);
break;
Не работает должным образом обмен данными. Например, не получается переключить режим работы. Не всегда получается перезагрузить ардуино при подключении.
Также, когда поставил обратную отправку получаемых данных на ардуино заметил, что обратная отправка данных осуществляется только при поступлении новых (код нажатия кнопки возвращается на комп только при нажатии следующей).
Текущий вариант кода не изначальный, к сожалению, я частично исковеркал его попытками все починить.
Ниже привожу код полностью и интерфейс программы на питоне.
Слезно прошу помощи, уже отчаялся
Весь код Python:
#Основной код проекта
from PyQt5 import QtWidgets, uic #Импорт необходимых библиотек
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
from PyQt5.QtCore import QIODevice
import pyqtgraph as pg
app = QtWidgets.QApplication([]) #создание окна приложения
ui = uic.loadUi("design.ui") #загрузка настроек интерфейса из файла
plot_widget = pg.PlotWidget() #создание виджета для графика
ui.plotLay.addWidget(plot_widget) #добавление графика в интерфейс
t = [] #массив значений времени
h = [] #массив значений управляющего воздействия
t.append(0) #добавление в массив начальной точки
h.append(0) #добавление в массив начальной точки
data = ""
plot = plot_widget.plot(t,h) #создание объекта-графика
plot_widget.showGrid(x=True, y=True, alpha=1.5) #вывод сетки на графике
plot_widget.setYRange(0, 10000, padding=0) #установка границ графика
error = QtWidgets.QMessageBox() #создание окна ошибки
error.setWindowTitle("Ошибка") #заголовок окна
error.setIcon(QtWidgets.QMessageBox.Warning) #иконка окна
serial = QSerialPort() #создание объекта для работы через последовательный порт
serial.setBaudRate(9600) #настройка скорости обмена данными
#Функции необходимые для работы
def serialSend(key, data): #функция для отправки данных на ардуино
txs = str(key) + "," + str(data) + ";" #сборка сообщения в соответствии с протоколом
serial.write(txs.encode()) #отправка сообщения
#print("python sends: ", txs)
def onRead(): #функция для чтения входящих сообщений
rx = str(serial.readLine(), "utf-8").split(';')[0] #чтение входящего текста
print(rx)
if rx.split(',')[0] == "0": #при дескрипторе 1 (в процессе производится разделение по "," и определение дескриптора)
ui.connL.setText("Connected") #вывод статуса соединения "Connected"
elif rx.split(',')[0] == "1": #при дескрипторе 3 (в процессе производится разделение по "," и определение дескриптора)
ui.curr_h.setText(rx.split(',')[1])
ui.F1.setText(rx.split(',')[2])
#ui.F2.setText(rx.split(',')[3])
elif rx.split(',')[0] == "2": # при дескрипторе 3 (в процессе производится разделение по "," и определение дескриптора)
ui.mim_st.setText(rx.split(',')[1])
ui.meo_st.setText(rx.split(',')[2])
t.append(float(rx.split(',')[2])) #запись значения времени
h.append(float(rx.split(',')[1])) #и управляющего воздействия
#dsolve(f, y[len(y)-1], x[len(x)-2]) #интегрирование управляющего воздействия
#y.append(soln[1][len(soln[1])-1]) #запись результата в выходную переменную
#serialSend(3, '%.3f' % y[len(y)-1]) #отправка полученного значения
#ui.curzadL.setText('%.3f' % y[len(y)-1]) #вывод текущего значения регулируемой величины
#ui.mistL.setText('%.3f' %(sp - y[len(y)-1]))#вывод текущей ошибки регулирования
plot.setData(t,h) #обновление данных графика
if len(t) > 100: #удаление первых элементов массивов, при размере больше 100
t.pop(0)
h.pop(0)
#Функции нажатий на кнопки
def disconnect(): #нажатие на кнопку "Disconnect"
serialSend(0, 1) # отправка на ардуино
serial.close() #закрытие соединения
ui.connL.setText("not connected") #обновление статуса на "not connected"
t.clear() #обнуление массивов
h.clear()
t.append(0) #добавление начальной точки
h.append(0)
def refresh(): #нажатие на кнопку "Refresh"
portList = [] #массив со списком доступных для подключения портов
ports = QSerialPortInfo.availablePorts() #получение информации о доступных портах
for port in ports: #заполнение массива
portList.append(port.portName())
ui.comL.clear() #очистка списка портов в интерфейсе
ui.comL.addItems(portList) #заполнение его данными из массива
if len(ports) == 0: #если нет доступных портов
disconnect() #вызвать функцию отключения
def connect(): #нажатие на кнопку "connect"
if len(QSerialPortInfo.availablePorts()) != 0: #если есть доступные порты
serial.setPortName(ui.comL.currentText()) #выбор имени порта в соответствии с выбранным в списке
serial.open(QIODevice.ReadWrite) #открытие соединения
serial.setDataTerminalReady(False)
serialSend(0, 0) # отправка на ардуино
def save():
global data
if serial.isOpen(): # если есть соединение
try: # конструкция для отлова ошибок
data = str(float(ui.h0.text().replace(",", "."))) # перевод текста из поля ввода в число
if ui.rezhim1.isChecked():
data = data + ",1"
elif ui.rezhim0.isChecked():
data = data + ",0"
else:
error.setText("Выберите режим!") # вывести окно ошибки с текстом "Выберите режим!"
error.exec()
serialSend(1, data) # отправка на ардуино
except ValueError: # при ошибке перевода
error.setText("Введите число!") # вывести окно ошибки с текстом "Введите число!"
error.exec()
else: # если контроллер не подключен, вывести соответствующую ошибку
error.setText("Контроллер не подключен")
error.exec()
def save_param():
global data
if serial.isOpen(): # если есть соединение
try: # конструкция для отлова ошибок
data = str(float(ui.de.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.he.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.hd1.text().replace(",", "."))) + "," # перевод текста из поля ввода в число
data = data + str(float(ui.hd2.text().replace(",", "."))) # перевод текста из поля ввода в число
serialSend(2, data) # отправка на ардуино
except ValueError: # при ошибке перевода
error.setText("Введите все параметры корректно!") # вывести окно ошибки с текстом "Введите все параметры корректно!"
error.exec()
else: # если контроллер не подключен, вывести соответствующую ошибку
error.setText("Контроллер не подключен")
error.exec()
def start():
global data
if serial.isOpen(): # если есть соединение
serialSend(3, int(ui.start.isChecked())) # отправка на ардуино
else: # если контроллер не подключен, вывести соответствующую ошибку
error.setText("Контроллер не подключен")
error.exec()
ui.disconnB.clicked.connect(disconnect) #подключение функций к кнопкам
ui.refrB.clicked.connect(refresh)
ui.connB.clicked.connect(connect)
ui.save.clicked.connect(save)
ui.start.clicked.connect(start)
ui.save_param.clicked.connect(save_param)
serial.readyRead.connect(onRead) #подключение функции чтения сообщений
def main():
refresh() #вызов функции обновления
ui.show() #вызов интерфейса
app.exec() #вызов окна приложения
if [B]name[/B] == "[B]main[/B]":
main()
Весь код Ардуино:
#include "GParser.h" //подключение библиотеки для парсинга данных
#include "avr/wdt.h"
//Исходные данные
//Емкость
float de = 7.58; //диаметр емкости
float se = 3.14*(de/2)*(de/2); //площадь сечения емкости
float he = 8.83; //высота емкости
//патрубки
float d1 = 0.025; //внутренний диаметр входного патрубка
float d2 = 0.025; //внутренний диаметр выходного патрубка
float s1 = 3.14*(d1/2)*(d1/2); //площадь входного отверстия
float s2 = 3.14*(d2/2)*(d2/2); //площадь входного отверстия
float hd1 = 0; //высота расположения входного патрубка
float hd2 = 0; //высота расположения выходного патрубка
//Датчики положения
#define MIM A0 //датчик положения МИМ
float prev_mim_st = 1, mim_st = 1; //степень открытия МИМ
#define MEO A1 //датчик положения МЭО
float prev_meo_st = 1, meo_st = 1; //степень открытия МЭО
int mim=0, mim_i=0, meo=0, meo_i=0; //промежуточные переменные
float k = 0.01; //коэффициент сглаживания
int p = 987; //плотность жидкости
float Kvy = 16; //условная пропускная способность клапана
float Kvy_i = 16; //условная пропускная способность при данной степени открытия
//Программные переменные
float V1 = 0; //Скорость наполнения через входной патрубок
float V2 = 0; //Скорость истечения из выходного патрубка
float F1 = 0; //Расход на входе
float prev_F1 = 0;
float F2 = 0; //Расход на выходе
float prev_F2 = 0;
float g = 9.81; //ускорение свободного падения
float dP = 0; //общее гидравлическое сопротивление канала
float dPkl = 0; //гидравлическое сопротивление клапана
float dPe = 0; //гидравлическое сопротивление столба жидкости
//управление симуляцией
boolean state = true; //состояние: 1 - норма, 0 - авария
boolean rezhim = false; //режим: 1 - налив, 0 - слив
boolean start = false; //начало симуляции
float h = 4; //текущий уровень в емкости
float h_ekv = 4; //уровень в емкости с учетом гидравл сопротивл клапана
float prev_h = 1.01; //предыдущее значение этой переменной
float V = se*h; //текущий объем
uint32_t timing = millis(); //переменная для начала отсчета таймера 1
uint32_t timing2 = millis(); //переменная для начала отсчета таймера 2
int stepp = 1; //коэффициент ускорения/замедления переходного процесса
void setup() { //настройки для обмена информацией между ардуино и компьютером
wdt_disable();
Serial.begin(9600);
Serial.setTimeout(50);
Serial.flush();
//Serial.println("h,F2,F1");
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
void loop() {
//if (Serial.available()>0) { //если на плату пришел сигнал с компьютера
//parser();
//}
if (millis() - timing > 100){ //таймер, запускающий команды ниже каждые 100 милисекунд
timing = millis();
if (start) {
float t = (float(millis() - timing2)/1000);
if (test()){
if (rezhim == 1) {
naliv();
Serial.println("naliv");
F2 = 0.0001;
stepp = 1;
} else {
sliv();
Serial.println("sliv");
F1 = 0.0001;
stepp = 500;
}
uroven();
//klapan ();
}
print_inf();
}
}
}
//Опустошение емкости
void sliv () {
dPe = p*g*(h-hd2);
Kvy_i = mim_st*Kvy;
dPkl = sq(F2)/sq(Kvy_i)*1000000;
dP = dPe - dPkl;
h_ekv = dP/(p*g);
V2 = sqrt(2*g*(h_ekv-hd2)); //расчет скорости истечения жидкости в соответствии с формулой
F2 = s2*V2; //пересчет скорости в объемный расход
}
//Наполнение емкости
void naliv (){
dPe = p*g*(h-hd1)/1000000;
Kvy_i = mim_st*Kvy;
dPkl = sq(F1)/sq(Kvy_i);
dP = dPkl + dPe;
F1 = sqrt((0.0974-dP)/0.012);
}
//расчет уровня из расхода на входе и на выходе
void uroven () {
h = (V - (F2-F1)*stepp)/se; //расчет текущего уровня в сосуде
V = se*h; //расчет текущего объема в сосуде
}
//отправка сообщений на компьютер
void print_inf(){
Serial.print(1); //отправка дескриптора на компьютер
Serial.print(",");
if (isnan(h)||h<0){ h = prev_h;} else { prev_h = h;} //конструкция, необходимая для обхода ошибки (в новых версиях Arduino IDE число не может быть равно 0, вместо этого выводится Nan
Serial.print(h*1000); //отправка текущего значения уровня на компьютер
Serial.print(",");
if (isnan(F1)){ F2 = prev_F1;} else { prev_F1 = F1;} //то же аналогично для расхода жидкости на входе и на выходе
Serial.print(F1,4);
Serial.print(",");
if (isnan(F2)){ F2 = prev_F2;} else { prev_F2 = F2;}
Serial.print(F2,4);
Serial.println(";");
Serial.print(2); //отправка дескриптора на компьютер
Serial.print(",");
if (isnan(mim_st)){ mim_st = prev_mim_st;} else { prev_mim_st = mim_st;}
Serial.print(mim_st*100,0);
Serial.print(",");
if (isnan(meo_st)){ meo_st = prev_meo_st;} else { prev_meo_st = meo_st;}
Serial.print(meo_st*100,0);
Serial.println(";");
}
//регулирование клапанами
void klapan () {
mim_i = analogRead(MIM); //чтение сигнала с переменного резистора
mim += (mim_i - mim) * k; //экспоненциальный фильтр для сглаживания шумов
mim_st = map(-mim, -250, -5, 0, 1); //преобразование измерений в степень открытия //НАСТРОИТЬ
meo_i = analogRead(MEO); //чтение сигнала с переменного резистора
meo += (meo_i - meo) * k; //экспоненциальный фильтр для сглаживания шумов
meo_st = map(-meo, -250, -5, 0, 1); //преобразование измерений в степень открытия //НАСТРОИТЬ
}
//проверка ограничений
boolean test() {
if (h < he - 0.3) {
state = true;
} else {
state = false;
V1 = 0;
F1 = 0;
}
return state;
}
//чтение входных данных
void serialEvent() {
while (Serial.available()) {
char str [15]; //создание символьного массива из 15 элементов
int amount = Serial.readBytesUntil(';', str, 15); //запись входных данных в массив
Serial.flush();
str[amount] = NULL; //удаление символа конца строки (?)
Serial.println(str);
GParser data(str, ','); //создание объекта GParser из массива str с символом разделителем ','
data.split(); //разделение полученного текста на подстроки, разделенные ','
switch (data.getInt(0)) { //выбор дальнейших действий в зависимости от значения первого полученного символа (дескриптора)
case 0:
if (data.getInt(1) == 0){
Serial.println("0,0");
} else if (data.getInt(1) == 1){
wdt_enable(WDTO_1S);
}
break;
case 1: //если он равен нулю
rezhim = data.getInt(1); //ПРОВЕРИТЬ РАБОТАЕТ ЛИ ТАК
h = data.getInt(2)/1000;
break;
case 2: //если он равен 1
de = data.getInt(1)/1000; //запись диаметра емкости
he = data.getInt(2)/1000; //запись высоты емкости
hd1 = data.getInt(3)/1000; //запись высоты входного отверстия
hd2 = data.getInt(4)/1000; //запись высоты выходного отверстия
break;
case 3: //если он равен нулю
start = data.getInt(1);
break;
}
}
}
Вложения
-
9.5 KB Просмотры: 1
-
20.2 KB Просмотры: 1
-
10.5 KB Просмотры: 2
-
9.6 KB Просмотры: 1
-
20.2 KB Просмотры: 1
-
10.5 KB Просмотры: 1