И так,
У нас имеется 4 класса для облегчения работы со схемами и 3 программы.
Начнем с программ:
- Официальный билд
- Debugger
- Viewer
Дебаггер нужен для отлова ошибок связанных именованием сегментов общей памяти, к тому-же дебаггер скомпилирован с закомментированной функцией, необходимой для работы алгоритма оптимизации, поэтому не следует включать в комплект вашего локомотива дебаггер.
Для работы схемы на стороне пользователя используется viewer, урезанный по гуи и редактированию схем дебаггер с алгоритмом оптимизации.
А теперь передем к программной реализации всего этого.
Как это работает? Очень просто, во viewere, вместе с созданием экземпляра класса, создается сегмент общей памяти с, внимание ID, нет не именем, а id.
Его нельзя менять, но определяется при инициализации объекта, вытащив его с панельки или открыв файл готовой схемы, и тут есть загвоздка. Дело в том, что если мы схему изменили, а при открытии из файла объекты инициализируются хаотично, то все ID смещаются, поэтому есть ограничения и добавить элемент в схему, не проинформировав главную программу (наш локомотив) измененными именами, нельзя. Важно так-же переоткрыть схему над которой мы работаем, ибо при редактировании объекты инициализируются в порядке их вытаскивания. Еще одна проблема, это самопроизвольное открытие схемы из backup файла, каждые несколько минут программа сохраняет изменения в неком буферном backup файле, но даже если мы прямо укажем открыть конкретный файл, то программа полезет за бэкапом сначала. Это порождает очень нехороший, нельзя назвать это багом, но при повторном открытии сразу после сохранения данного файла программа инициализирует элементы с совсем другими id, сначала вообще кажется, что id присваиваются рандомно, но долго переоткрывать не придется. Закономерность прослеживается уже после повторного переоткрытия файла: все встает на свои места, id становятся как при первом открытии, сразу после запуска.
Какой вывод можно сделать? Viewer не читает бэкап, в локомотиве ID (они же Key для памяти) прописывайте те, что даются при первом открытии файла (запускаем оф. билд -> открываем наш файл -> смотрим ID -> запоминаем -> выходим без сохранения)
Так же во вьювере нет интерфейса, только схема. Запускает симуляцию автоматически и файл открывает тоже автоматически. Управлять им можно из ini, который лежит около него. В path прописываем полный путь до файла проекта (схемы) включая имя. Остальные переменные трогать не надо! Раасскажу о них потом.
Рассмотрим базовый класс для работы с памятью.
Как уже писал выше инициализация памяти происходит автоматически, на стороне симулятора схем. Соответственно, имя задается тоже там, по ID.
Описывать, что я делал во viewere не буду, расскажу только как с ним работать, но если кому-то нужно или интересно, то пишите, я выложу измененные исходники viewera.
Смотрим header базового класса:
Код: Выделить всё
#ifndef SHAREDMEMGETSET_H
#define SHAREDMEMGETSET_H
#include <QSharedMemory>
class SharedMemGetSet
{
public:
SharedMemGetSet(QString memname);
~SharedMemGetSet();
template <typename T>
T getFromMem(){
m_sharedmem.lock();
T value = 0;
T *from = (T*)m_sharedmem.data();
value = *from;
m_sharedmem.unlock();
return value;
}
template <typename K>
void setMem(K valtoset){
m_sharedmem.lock();
K *to = (K*)m_sharedmem.data();
*to = valtoset;
m_sharedmem.unlock();
}
private:
QSharedMemory m_sharedmem;
};
#endif // SHAREDMEMGETSET_H
Как работать?
( создаем экземпляр -> кидаем в конструктор стринг ключа (тот самый ID) -> работаем с памятью с помощью этих функций)
Рассмотрим два более сложных класса: пакетник и multiswitch:
Multiswitch лежит в основе, поэтому смотрим его хедер первым:
Код: Выделить всё
#ifndef MULTISWITCH_H
#define MULTISWITCH_H
#include <QObject>
#include "SharedMemGetSet.h"
class MultiSwitch : public QObject
{
Q_OBJECT
public:
MultiSwitch(const QList<QString>& arg, int rows, int cols, const bool* arr, QObject *parent); // конструктор для карты положений
~MultiSwitch();
bool setPos(int pos); // единственная функция, меняет положение пакетника по аргументу
private:
int m_rows;
int m_cols;
const bool* arr_ptr;
const QList<QString>* m_ids;
};
#endif // MULTISWITCH_H
Первый параметр конструктора принимает массив QList<QString> , в котором по порядку обозначаем ID переключателей.
Второй параметр принимает значения рядов (количество положений нашего переключателя) главной карты положений или просто двумерного массива.
Далее следует параметр колонок (количество контактов) главной карты положений или просто двумерного массива.
А теперь уже следует наша главная карта положений переключателя. Что она из себя представляет? Простой двумерный bool массив. Возможности не ограничены! Как это работает? Очень просто! В конструктор мы передаем указатель на нулевое значение! &arr[0][0] Далее единственной функцией кидаем по порядку из заданного ряда значения в общюю память каждого переключателя по циклу, попутно хватая значения ID из первого массива.
Для большего понимания картиника:
Где id1, 2...4 ID выключателей на которые мы отошлем соответствующее ряду (положению) bool значение.
Еще нужно отметить, что лучше инициализировать массив как показано на рисунке, еще бы я объявил не bool а static inline const bool прямо в хедере класса в котором мы бы инициализировали этот, но inline требует включения C++17, что можно с легкостью сделать в qt. Передал бы указатель через &, а в параметры rows и cols, Xrows и Xcols соответственно. В механизме нет защиты от неверных параметров, в частности rows и cols, неверные значения которых будут приводить к падению программы! Но присутствуют механизмы защиты от неверного аргумента для функции изменения положения, да она сама bool и возвращает false, если аргумент меньше нуля или больше чем рядов в массиве. Здесь еще нужно сказать, что нумерация в массиве начинается с нуля, а в аргумент мы передаем на основе нумерации с одного. То есть программно первое положение преключателя это 0, а в аргумент мы передаем именно 1 как первое положение, а не ноль!
Методы:
Код: Выделить всё
#include "multiswitch.h"
#include "SharedMemGetSet.h"
#include <QList>
MultiSwitch::MultiSwitch(const QList<QString>& arg, int rows, int cols, const bool* arr, QObject *parent) :
QObject(parent), m_rows(rows), m_cols(cols), arr_ptr(arr), m_ids(&arg)
{
}
bool MultiSwitch::setPos(int pos){
if(pos <= 0 || pos > m_rows){ return false;}
pos--;
for(int i = 0; i < m_cols; i++){
SharedMemGetSet mem((*m_ids)[i]);
mem.setMem(*((arr_ptr+i)+(m_cols*pos)));
}
return true;
}
MultiSwitch::~MultiSwitch(){}
Для клавиатурной обработки есть спец класс:
Передем к "Пакетнику"
Смотрим хедер:
Код: Выделить всё
class Paketnik : private MultiSwitch
{
Q_OBJECT
public:
explicit Paketnik(const QList<QString>& arg, int xn, int xm, const bool* xA ,QObject *parent = nullptr);
int getCurrPos() {return m_currpos;};
void setPosition(int pos);
private:
QTimer *tmr;
bool m_allowed;
int m_currpos;
public slots:
void chngUP();
void chngDW();
void allow();
signals:
};
#endif // PAKETNIK_H
А сейчас я расскажу про слоты.
Код: Выделить всё
#include "paketnik.h"
Paketnik::Paketnik(const QList<QString>& arg, int xn, int xm, const bool* xA, QObject *parent) :
MultiSwitch( arg,xn,xm, xA, parent)
, m_allowed(true)
, m_currpos(1)
{
tmr = new QTimer();
tmr->setInterval(500);
connect(tmr, SIGNAL(timeout()), this, SLOT(Allow()));
}
void Paketnik::chngUP(){
if(m_allowed){
if(setPos(m_currpos + 1)){
m_currpos++;
}
m_allowed = false;
tmr->start();
}
}
void Paketnik::chngDW(){
if(m_allowed){
if(setPos(m_currpos - 1)){
m_currpos--;
}
m_allowed = false;
tmr->start();
}
}
void Paketnik::allow(){
tmr->stop();
m_allowed = true;
}
void Paketnik::setPosition(int pos){
setPos(pos);
m_currpos = pos;
}
Конструктор идентичен multiswitch.
Передем к самому сырому классу азв:
Данный класс симлирует работу азв. Симулирует довольно примитивно, но работает стабильно. Подробнее я уже его описывал в видео. В симуляторе схем он реализуется как амперметр и выключатель. В конструктор принимает несколоко параметров:
Либо два стринга либо массив из них, зависит от конструкора, но на первом месте всегда ID амперметра, а на втором ID переключателя.
Далее следует номинал в амперах.
Далее следует множитель, во сколько раз нужно превысить номинал для срабатывания быстрой защиты.
Далее следует время отключения когда номинал превышен незначительно. Сделано простой ступенькой, без функций.
Вот хедер:
Код: Выделить всё
#ifndef AZV_H
#define AZV_H
#include "SharedMemGetSet.h"
#include <QObject>
#include <QTimer>
class Azv : public QObject
{
Q_OBJECT
public:
Azv(QString idAmper, QString idSwitch, int nomcurr, int fastoffmultiplier, int timetooff, QObject *parent = 0);
Azv(const QList<QString>& arg, int nomcurr, int fastoffmultiplier, int timetooff, QObject *parent = 0);
bool getState(){ return sw.getFromMem<bool>();};
double getCurr(){ return amper.getFromMem<double>();};
void check();
public slots:
void setON();
void setOFF();
void changeVal();
protected:
SharedMemGetSet amper;
SharedMemGetSet sw;
QTimer *tmr;
int m_fastoffmult;
int m_expmult;
int m_nomcurr;
bool m_tmrstarted;
};
#endif // AZV_H
Вот и все!
Это своеобразная бета комплекта. Багов будет немеренно! Выкладываю, потому как нету времени самому тестить очень много.
Конечно-же приветствуется критика, особенно если еще и с советами!
--------------------------- Перезалив!---------------------------
https://drive.google.com/file/d/1mzpM0H ... sp=sharing 7z
--------------------------- Теперь исходник доступен на github!---------------------------
https://github.com/asafran/simulide-rrs-viewer
В архиве 5 папок
viewer
debugger
оф билд
и програмка, ужасно кривая, основанная на официальном примере sharedmem
elements - папка с хедерами и методами
Пойду покушаю и потом еще пример применения этого всего опишу!