Поле структуры класс

AsNik
Offline
Зарегистрирован: 24.10.2020

Возвращаюсь к вопросу. На данный момент пришел к тому, что у меня не все датчики одного типа оказались. Отсюда в структуре поле 

struct DHTData {
02   DHT * dht;
03   char dhtTitle[20];
.....
   
09   bool f_DHTError;
10

};

DHTData dhts[COUNT_DHT];

...........

1 for (byte n = 0; n < COUNT_DHT; n++) {
2   dhts[n].dht = new DHT(dhtPins[n], dhtType[n]);
3   dhts[n].dht->begin();
4 }

DHT * dht; - нужно как-то изменить.

Про вариант DetSimen'а помню это про отдельный класс, но пока хочу попробовать сделать "по своему" ибо в текущем коде хоть как-то с горем пополам разбираюсь. Хотя уже пару раз делал глобальные изменения, думал рухнет все.... ан-нет.

Так вот у меня вопрос: Каким образом можно переделать. В структуре поле DHT * dht; думаю будет void * sens;

Создание тоже думаю не возникнут вопросы: типа

switch (typesens)

case dht: dhts[n].sens= new DHT(dhtPins[n], dhtType[n]); break; - или так не пойдет?

case bmp; ... break;

case am: .... break;

ну и дальше как этот sens приводить к нужному классу (ну типа как в делфи temp:=(sens as TMySens).ReadTemp; про предварительный is тоже знаю...)

В общем вопрос, как "грамотно" это сделать? ("грамотно" в кавычках - это я понимаю, что грамотно будет типа как было предложено отдельным классом для каждого типа датчика, но...)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Грамотно - через наследование классов

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

AsNik пишет:

Создание тоже думаю не возникнут вопросы: типа

switch (typesens)

case dht: dhts[n].sens= new DHT(dhtPins[n], dhtType[n]); break; - или так не пойдет?

case bmp; ... break;

case am: .... break;

AsNik
Offline
Зарегистрирован: 24.10.2020

ЕвгенийП пишет:

Грамотно - через наследование классов

Это я знаю и понимаю. Буду стремиться к этому, но на данный момент хотелось бы узнать как нетипизированный указатель привязать к разного типа объектам и потом с ними работать. Спасибо

AsNik
Offline
Зарегистрирован: 24.10.2020

DetSimen пишет:

:( Т.е. то как я хочу это сделать - не получится. Только переделывание всего проекта с нуля всё и на отдельных классах? Или из-за моих "тупых" вопросов мне больше не стоит ждать помощи?(

AsNik
Offline
Зарегистрирован: 24.10.2020

AsNik пишет:

:( Т.е. то как я хочу это сделать - не получится.

Сделал вот такой тестовый пример:

#include "DHT.h"
#include <Servo.h>

#define CNT 2
struct TestData {
  void * sens;
  int Err;
};
TestData dt[CNT];

void setup() {
  Serial.begin(115200);
  dt[0].sens = new DHT(5, DHT11);
  dt[1].sens = new Servo;
  static_cast<Servo*>(dt[1].sens)->attach(9);
}

void loop() {
  delay(1000);
 float t = static_cast<DHT*>(dt[0].sens)->readTemperature();
 static_cast<Servo*>(dt[1].sens)->write(random(180));
 Serial.println(t);
}

Работает. Вопрос: что плохого/хорошего в этом способе? Или работает - это просто мне повезло?)

b707
Offline
Зарегистрирован: 26.05.2017

AsNik пишет:

Работает. Вопрос: что плохого/хорошего в этом способе?

с точки зрения синтаксиса языка - это допустимо, хотя и попахивает. Но с точки зрения обычной логики - это глупость. Зачем обьединять в одном классе термометр и серву, какой в этом смысл? Классы нужны для того.,чтобы проще работать с обьектами.  А если при любом вызове метода вам придется насильно приводить тип сенсора либо к термометру либо к серве - зачем нужен такой общий класс. не проще ли пользоваться сразу классами термометра и сервы?

AsNik
Offline
Зарегистрирован: 24.10.2020

b707 пишет:

Зачем обьединять в одном классе термометр и серву, какой в этом смысл? 

Дак тут смысл не в серве и термометре. Они - это что первое мне под руку попалось. Смысл данного кода в нетипизированном указателе и его приведении к нужному типу.

b707 пишет:

не проще ли пользоваться сразу классами термометра и сервы?

Скорее всего проще. На данном моменте моего проекта я это уже осознаю. Но переделывать с "нуля" пока не решаюсь. Но буду. Но и данный проект принес мне много нового и поучительного. Конкретно сейчас я понимаю, что если делать сначала то многие вещи нужно делать по другому. Но пусть здесь будет пока так. Возможно и следующий проект у меня не получится "грамотным", даже скорее всего не получится, но я разбираюсь в этом и стараюсь к этому (грамотному написанию) придти. Многие вещи я даже сейчас вижу как переделать в моем проекте, может потихоньку, не разом, но переделаю....

Спасибо что читаете и отвечаете на мои сообщения. Но жаль что уровень моих знаний пока не дотягивает до уровня ваших подсказок.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

AsNik пишет:
Смысл данного кода в нетипизированном указателе и его приведении к нужному типу.
Необходимость в таком указателе почти всегда означает, что Вы делаете что-то не так. Это верный признак говнокода.

AsNik
Offline
Зарегистрирован: 24.10.2020

b707 пишет:

Зачем обьединять в одном классе термометр и серву, какой в этом смысл?

Вообще там будут только датчики (темпер/влаж) только разные. Изначально думал будут одного типа(DHT22) все, но оказалось, что у меня их несколько и все они разные(BME, AM ну и DHT). Поэтому вот это:

struct DHTData {                    //Данные датчика
  DHT * dht;
  char dhtTitle[20];                      //Описание датчика
  int  tOn, tOff, hOn, hOff;              //Температура включения/выключения, Влажность включения/отключения
  int t = MYNAN, h = MYNAN;               //Текущая температура и влажность
  int prevT = MYNAN, prevH = MYNAN;       //Предпоследние значения температуры и влажности (для определения динамики)
  int8_t tDyn = 0, hDyn = 0;              //Динамика изменения температуры и влажности
  int ttmp = 0, htmp = 0;                 //Временные данные по температуре и влажности нужны в момент измерения
  int tmin, tmax, hmin, hmax;             //Минимальные и максимальные значения температуры и влажности
  byte cnt[2][3];                         //Счетчики срабатываний каналов сегодня/вчера/позавчера
  uint32_t workTime[2];                   //Продолжительность работы каналов, по завершению плюсуется к timesWork[0]
  uint32_t workTimes[2][3];               //Общее время работы каналов сегодня/вчера/позавчера
  bool isWorkCh[2];                       //Признак работы каналов
  bool f_DHTError;                        //True Если с датчика DHT ничего не считалось
};

DHTData dhts[COUNT_DHT];                                  //Массив данных датчиков

Хочу переделать вот в это:

struct DHTData {                    //Данные датчика
  void * dht;
  char dhtTitle[20];                      //Описание датчика
  int  tOn, tOff, hOn, hOff;              //Температура включения/выключения, Влажность включения/отключения
  int t = MYNAN, h = MYNAN;               //Текущая температура и влажность
  int prevT = MYNAN, prevH = MYNAN;       //Предпоследние значения температуры и влажности (для определения динамики)
  int8_t tDyn = 0, hDyn = 0;              //Динамика изменения температуры и влажности
  int ttmp = 0, htmp = 0;                 //Временные данные по температуре и влажности нужны в момент измерения
  int tmin, tmax, hmin, hmax;             //Минимальные и максимальные значения температуры и влажности
  byte cnt[2][3];                         //Счетчики срабатываний каналов сегодня/вчера/позавчера
  uint32_t workTime[2];                   //Продолжительность работы каналов, по завершению плюсуется к timesWork[0]
  uint32_t workTimes[2][3];               //Общее время работы каналов сегодня/вчера/позавчера
  bool isWorkCh[2];                       //Признак работы каналов
  bool f_DHTError;                        //True Если с датчика DHT ничего не считалось
};

DHTData dhts[COUNT_DHT];                                  //Массив данных датчиков

Все одинаково, только вместо DHT * dht; будет void * dht;

Ну и по коду проделать некоторые изменения. Благо там их не должно быть много.

AsNik
Offline
Зарегистрирован: 24.10.2020

ЕвгенийП пишет:

Это верный признак говнокода.

Я это не для выставки или соревнования делаю, даже не для продажи;). Более того мне было просто интересно это сделать. Я не программист... было лет 20 назад хобби, но уже все на столько забыл, да и в си (да еще и не под винду) это немного по другому... Хорошо хоть подсказываете и на путь истинный ставите. Не думайте что я к критике плохо отношусь. Спасибо.

посмотреть бы на список "признаков" говнокода.... ну чтоб стараться так не делать. :) Просто для кого-то работа с указателями - это нормально. Для меня - это кошмар. А кто-то понимает где нужно работать с указателями а где не нужно.

У меня данный проект не большой, всего-то около 900 строк.... Но думаю использовав нетипизированный указатель я малой кровью отредактирую его для использования разного типа датчиков. И в таком небольшом проекте скорее всего это будет безболезненно. Тем более что проект развиваться и меняться не будет.

sadman41
Offline
Зарегистрирован: 19.10.2016

Я бы сделал проще. Структуру с типом датчика, его идентифицирующим атрибутом и полем значения метрики. Потом в процедуре полинга, опираясь на тип и атрибут (для DHT это пин, к примеру), опрашивал датчик и выгружал полученное в поле значения метрики.

AsNik
Offline
Зарегистрирован: 24.10.2020

Я может это как-то всё понимаю по другому, может быть термины не правильно понимаю типа (поле значения метрики, процедура полинга...).... Но с другой стороны мне кажется что у меня в принципе почти так и сделано. В моем проекте есть три датчика. Сейчас вот как раз сделаю в структуру еще идентификатор датчика (константный) Исходя из него будет в начале создан и присвоен указателю dht нужный тип датчика (BME и AM по айтуси (I2C) т.е. им даже пин не нужно назначать, но с этим вопросов нет вообще) и далее в методе чтения так же исходя из типа считаю с трех датчиков с каждого в свои переменные структуры - данные с датчиков. Т.е. просто заполняется данными массив структур. А дальше в методе луп будет вызвана процедура Анализа данных (это в  WorkChannelTimer())и затем процедура отрисовки экрана (Эх, вот ее мне точно нужно оптимизировать, разрослась дай боже...). Вот соб-но сам луп:

void loop() {                                                 // ---------- LOOP -------------------

  readEncoder();                //Читаем энкодер
  readDHTs();                    //Считываем датчики

  VerifyResetTimer();           //Таймер для сброса данных раз в сутки, так же используется для выключени дисплея при бездействии
  verifyEditTimer();            //Таймер для автовыхода из режима редактирования
  WorkChannelTimer();           //Таймер работы каналов
  UpdateScreenTimer();          //Запрос обновления экрана
}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Все это суета сует. Просто три инстанса или три инстанса через указатели в массиве структур... Большого выигрыша ни по скорости, ни по размеру кода не будет. Динамически массив датчиков перестраиваться не будет, а без него указатели, кастинги и пр. - ментальные упражнения в попытке убить время.

AsNik
Offline
Зарегистрирован: 24.10.2020

Все может быть. Но так как у меня нет опыта в таких вещах, изначально думал, что массив - будет все упорядочено и легче работать. И оно может так и было бы если датчики все были одного типа.

А указатели, кастинги и убийство времени - это верно, я не отрицаю. Мне это было очень интересно в плане получения опыта.

sadman41
Offline
Зарегистрирован: 19.10.2016

Массив удобен для однотипных данных, обрабатываемых эквивалентно. Например - все температуры распечатать с 50ти датчиков. А когда в структуре разносол поштучный - то ее обработка раздувается похлеще живота программера в пятницу.

Хотите массив - заведите массив значений метрик. Хотя и там тоже наборы разноплановые... Красиво, в строчку, не выйдет, энивэй. Полезут уродливые кейсы, ифы.

AsNik
Offline
Зарегистрирован: 24.10.2020

Так в том и дело что у меня в структуре данные касаются только одного датчика, а по датчикам они однотипные, поэтому они в массиве. Чтение и наполнение данными структуры тоже однотипные для всех датчиков. А вот работа с этими данными уже будет для каждого датчика своя. И даже данные одного датчика могут использоваться в обработке данных с другого. У каждого датчика по два канала.

Например один датчик у меня установлен в кессоне. И вот его основная задача держать температуру выше ноля(точнее выше +2..2,5°С) (Первый канал) Подогрев воздуха - банально лампы накаливания две штуки по 200W. (две - на всякий случай если одна сгорит. Доступ в кессон зимой очень проблемный.) А второстепенная (второй канал) - это влажность. Включается вентилятор. И вот тут пригодится датчик с улицы. Если на улице высокая влажность то и нет смысла вертеть вентилятором.... как-то так. Но как обычно всех нюансов в одном сообщении не укажешь.

Там такие нюансы, что если низкая температура но еще не критичная что бы включить обогрев, но высокая влажность.... Вроде просто включаем вентилятор и все. Но нет, нужно сначала поднять температуру, а потом продуть все это дело.... В общем как-то так .... может я и занимаюсь фигней, но мне это просто интересно, и самое главное надеюсь будет полезно) 

AsNik
Offline
Зарегистрирован: 24.10.2020

AsNik пишет:

И вот его основная задача держать температуру выше ноля(точнее выше +2..2,5°С) (Первый канал) Подогрев воздуха - банально лампы накаливания две штуки по 200W. 

Так-то кессон утеплен и по идее его греет скважина и температура не должна упасть ниже ноля. Но кессон не успели доделать как положено и вообще это первый опыт. Мало ли какие морозы будут зимой. А там труба, хоть и пвх, но... Поэтому я вспомнил про ардуину и решил немного подстраховаться в эту зиму. Заодно попробую возрадить хобби...)

sadman41
Offline
Зарегистрирован: 19.10.2016

Ну так я и понял, что у Вас много времени для выдумывания развесистых структур. Вместо заполнения двух переменных outdoorT и outdoorH готовы вертеть через три колена указатели на void.

Вы же не будете в цикле сравнивать температуру уличную и домовую. Налепите дерево if()-ов...

Впрочем, для того, чтобы осознать, что ООП - это не улучшающая приправа к любому блюду, нужна практика.

А то, что сейчас вижу - это оверинжиниринг.

AsNik
Offline
Зарегистрирован: 24.10.2020

sadman41 пишет:
Ну так я и понял, что у Вас много времени для выдумывания развесистых структур. Вместо заполнения двух переменных outdoorT и outdoorH готовы вертеть через три колена указатели на void.

Прошу прощения, но я не понял как можно данные (T и Н) с трех! датчиков сохранить в двух переменных??

Ну я имею ввиду простые типы типа Int... :)

sadman41 пишет:

Вы же не будете в цикле сравнивать температуру уличную и домовую. Налепите дерево if()-ов... 

Как раз наоборот по массиву легче "бегать". Вот например как у меня "предобработка" данных с датчиков:

uint32_t ms;
void WorkChannelTimer() {
  if (millis() - ms >= 1000) {
    {
      for (byte n = 0; n < COUNT_DHT; n++)
        if (!dhts[n].f_DHTError) for (byte ch = 0; ch < 2; ch++)        //Если у датчика нет ошибки
            if (dhts[n].isWorkCh[ch]) dhts[n].workTime[ch]++;           //и канал включен - считаем время работы
      static byte n = 0;
      if (n > 0) n--; else {
        n = 30;                       //Каждые 30 секунд
        AnalizeDHTs();                //Анализируем данные с датчиков
      }
    }
    do {
      ms += 1000;
      if (ms < 1000) break;         // переполнение uint32_t
    } while (ms < millis() - 1000); // защита от пропуска шага
  }
}

По моему удобно и ifы в меру надобности... Я не отрицаю и не спорю что я много не знаю и можно сделать все проще. Но надеюсь не забросить это дело и развиваться в этом направлении.

sadman41 пишет:

ООП - это не улучшающая приправа к любому блюду, нужна практика.

Практика - это тоже нужно. Тем более теорию я без практики вообще не понимаю.

Если посмотреть на мою структуру, то там избыточные данные, но это чисто для практики и моего любопытства... Например, посмотреть сколько раз за сутки включались лампы и сколько они горели по времени....

А так, да.... можно код сократить в разы...

sadman41 пишет:
Вместо заполнения двух переменных outdoorT и outdoorH готовы вертеть через три колена указатели на void.

Или Вы имели ввиду у каждого датчика по две переменные?

Тогда да, но.... я уже сказал, что избыточность данных по датчикам - это да, у меня много времени и любопытства :)

sadman41 пишет:
А то, что сейчас вижу - это оверинжиниринг.
Ох, не заметил ) Так-то верно. Но не ругайте меня за это ;)