Постоянный посетитель
|
ARDUINO для автоматизации аквариума (страница 8) |
Создал отдельную тему по вопросам использования простых и не дорогих плат ARDUINO для целей автоматизации аквариума. Схема коммутации в случае использования платы NANO: Программа для среды программирования Arduino: //*************************************************** // Скетч для управления релейным модулем, в котором * // использовано два канала. Используется также RTC * // Релейный модуль - с инверсной логикой на входе * // Автор: ZORS * // Версия 1. Дата 21.12.2013 02:40 * //*************************************************** //----------ИМПОРТ БИБЛИОТЕК------------------------- #include //Подключаем библиотеку для использования I2C интерфейса с модулем RTC #include //Подключаем библиотеку для использования модуля часов реального времени RTC RTC_DS1307 RTC; //Создаем переменную класса - для использования RTC //----------Объявляем разные переменные------------ const int RelayChn1 = 6; //Используем цифровой ПОРТ 6 для ПЕРВОГО канала релейного модуля const int RelayChn2 = 7; //Используем цифровой ПОРТ 7 для ВТОРОГО канала релейного модуля //----------Настройки времени и продолжительности включения реле //----------ПЕРВЫЙ канал---------------------------- const long StartRelCn_1 = 25200; //Время срабатывания в ПЕРВОМ канале релейного модуля (в секундах от начала суток) //в данном случае 25200 - это 7 часов 00 минут = ( 60секунд *60 минут *7 = 25200) const long DurationCh_1 = 10; //ДЛИТЕЛЬНОСТЬ срабатывания реле в ПЕРВОМ канале (в секундах) //----------ВТОРОЙ канал---------------------------- const long StartRelCn_2 = 37800; //Время срабатывания во ВТОРОМ канале релейного модуля (в секундах от начала суток) //В данном случае 10 часов 30 минут = (60 секунд * 60 минут * 10 часов + 60сек*30мин = 37800) const long DurationCh_2 = 15; //ДЛИТЕЛЬНОСТЬ срабатывания реле во ВТОРОМ канале (в секундах) //----------Модуль инициализации setup() - выполняется один раз при инициализации платы при подаче напряжение (и аналогичных событиях) void setup(){ pinMode(RelayChn1,OUTPUT); //Инициализируем порт для ПЕРВОГО канала как ВЫХОД pinMode(RelayChn2,OUTPUT); //Инициализируем порт для ВТОРОГО канала как ВЫХОД digitalWrite(RelayChn1,HIGH); //Устанавливаем на входах релейного модуля ВЫСОКИЙ уровень digitalWrite(RelayChn2,HIGH); //Т.к. используемый релейный модуль с опторазвязкой - управляется инверсной логикой Wire.begin(); //Инициируем I2C интерфейс RTC.begin(); //Инициирум RTC модуль // RTC.adjust(DateTime(__DATE__, __TIME__)); //С этой строки необходимо убрать комментарии один раз в начале, //для того, чтобы загрузить в RTC дату и время на момент компиляции программы //Иногда необходимо заливать СКЕТЧ на плату со снятым комментарием - для поправки //времени в RTC, НО оставлять такой СКЕТЧ в работе НЕЛЬЗЯ !!!!!!!!!!!!! } // КОНЕЦ ИНИЦИАЛИЗАЦИИ //-------------------------------------------------- void loop() // ПРОГРАММЫй безусловный ЦИКЛ { DateTime myTime = RTC.now(); //Читаем данные времени из RTC при каждом выполнении цикла //----------Раздел обработки реле по времени ---- long utime = myTime.unixtime(); //сохраняем в переменную - время в формате UNIX utime %= 86400; //Сохраняем в этой же переменной остаток деления на кол-во секнд в сутках, //Это дает количество секунд с начала текущих суток //------------КАНАЛ 1------------------------------ if ((utime >= StartRelCn_1) && (utime //Если секунд с начала суток больше, чем задано для включения //Но, одновременно и меньше, чем задано для включения + длительность { digitalWrite(RelayChn1,LOW); //Устанавливаем на ПЕРВОМ входе релейного модуля НИЗКИЙ уровень - реле срабатывает } else //во всех остальных случаях { digitalWrite(RelayChn1,HIGH); //Устанавливаем на ПЕРВОМ входе релейного модуля ВЫСОКИЙ уровень - реле выключается } //------------КАНАЛ 2 - все аналогично ----------- if ((utime >= StartRelCn_2) && (utime { digitalWrite(RelayChn2,LOW); //Устанавливаем на ВТОРОМ входе релейного модуля НИЗКИЙ уровень - реле срабатывает } else { digitalWrite(RelayChn2,HIGH); //Устанавливаем на ВТОРОМ входе релейного модуля ВЫСОКИЙ уровень - реле выключается } }//------------Конец ЦИКЛА----------------------------- (Редактор текста на данном сайте - к сожалению, "ломает" красивое форматирование. PS: Выложил этот скетч на файлообменник : http://my-files.ru/h... В данной программе реализован, довольно-таки простой подход, а простота - основа надежности. Каждое из 2-х реле срабатывают один раз в сутки в указанное время, причем время указывается в количестве секунд от начала суток, и удерживается указанное количество времени в секундах. Такой способ задания времени - несколько сложен для понимания, но за это - не нагружает программу и процессор лишним функционалом, который понадобится всего один раз. Для определения времени наступления события начала включения и выключения реле - используется время в формате UNIX. Такой подход хорош тем, что даже если включение или перезагрузка платы произойдет в промежутке времени включения, то реле включится в оставшееся время. В случае с дозированием, это не много что дает, но в случае, если по аналогии будет сделано управление светом, то при подаче напряжения на плату, например, после пропадания напряжения питания, или после посадки напряжения, или др. аналогичных событий, включение и выключение реле произойдет корректно. У комплектных минутных и секундных таймеров "с розеткой" - об этом можно только мечтать....не реализован такой функционал, и еще у них есть один минус. Реле в таких таймерах - не удерживаемое, а переключаемое, со всеми вытекающими последствиями.... Для программирования нескольких включений каждого реле в течение суток - естественно, потребуется несколько другой подход....спрашивайте когда кому-то потребуется, пока не хочу все усложнять... В принципе, можно добавить константных переменных, содержащих время и длительность срабатывания, и соответственно добавить проверку на условия. Если количество необходимых срабатываний каждого реле в сутках больше чем 2 - лучше оформить проверку условий в отдельной функции. Как, говорил человек, учивший меня программированию - "все, что ты делаешь в программе больше двух раз - оформляй отдельной процедурой, или функцией". Главное ограничение этого подхода - включение и выключение реле - должно произойти в одних сутках. Если необходимо чтобы, реле включилось в одних сутках, а выключилось в других - необходимо разбить это задание на 2 задания, или применить другой метод проверки условий наступления событий включения и выключения. Весь этот пример тестировался на "живом" железе : Для наглядности отладки был еще подключен LCD дисплей 16 сим в 2 строках. Схема итоговая была такая: Обратите внимание, что при использовании I2C последовательного интерфейса - соединения значительно упрощаются. И LCD дисплей подключен транзитом через модуль RTC (часов реального времени). Никаких паяных соединений нет вообще....все скоммутировано - проводами с разъемами. Скетч - естественно, несколько другой использовался с добавлением строк инициализации и использования LCD 16x2. Плата использовалась типа такой: http://www.ebay.com/... цена примерно 350 руб. Релейный модуль http://www.ebay.com/... цена примерно 150 руб. Модуль часов реального времени: http://www.ebay.com/... Цена примерно 55 руб. Для сокращения бюджета можно использовать плату проще: http://www.ebay.com/... Цена примерно 200 руб. Если есть потребность подключить ЖК дисплейчик, то лучше брать ЖК дисплей (LCD) c I2C интерфейсом - это сильно упрощает коммутацию, и сокращает количество использованных портов на микроконтроллере. Например такой можно использовать : http://www.ebay.com/... цена около 180 руб. На ЖК можно выводить статусную информацию, информацию об ошибках, диагностическую информацию. Все это, конечно, можно и в последовательный порт компьютера выводить, но это не всегда удобно (скорее наоборот), поэтому я всегда использую какой-нибудь девайс для вывода информации. Для использования I2C интерфейса - необходима библиотека Wire. Она есть в стандартном дистрибутиве среды программирования Arduino. Для использования модуля RTC на микросхеме DS1307 нужна библиотека RTClib. Если ее не будет в стандартном дистрибутиве, то ее можно взять в интернет. Например здесь : http://arduino-info.... Необходимо, скачать, и разархивировать zip-файл в папку с понятным названием, например RTCLib. Затем эту папку поместить в папку, где среда Arduino хранит свои библиотеки. Если ставили среду программирования с настройками по умолчанию, то этой папкой будет : C:Program FilesArduinolibraries Сюда и помещайте папки с подключаемыми библиотеками. Если на этот момент, среда программирования была запущена - ее необходимо закрыть, и запустить заново. После этого библиотека будет доступна в меню Скетч/Импортировать библиотеку. Микросхема RTC модуля DS1307, как правило использует адрес на шине I2C равный 0x68 поэтому, необходимо проверить в библиотечных файлах, библиотеки RTClib наличие строки типа этой: #define DS1307_ADDRESS 0x68 Если будет использоваться LCD 16x2 с I2C интерфейсом, то - понадобиться библиотека LiquidCrystal_I2C. Можно скачать здесь : http://dvrobot.ru/lc... Как подключить к среде программирования Arduino - см. выше ВАЖНО : при использовании I2C устройств - в скетче, библиотека Wire - должна быть объявлена первой, иначе - будут ошибки компиляции. По этому примеру - все.... в следующем - присоединение термо-датчика DS18B20, и создание несложного терморегулятора..... Изменено 24.12.13 автор Z0RS |
|
#1907148 |
Посетитель
|
|
arch07 Все добавляю. как установить билиотеки я нашел. Только вот при добавлении библиотвеки LiquidCrystal_I2C из первого поста, ругается: In file included from SunRiseSunSet.ino /Users/Mac/Documents/Arduino/libraries/LiquidCrystal_I2C/LiquidCrystal_I2C.h error: conflicting return type specified for 'virtual void LiquidCrystal_I2C::write(uint8_t)' /Users/Mac/Downloads/Arduino.app/Contents/Resources/Java/hardware/arduino/avr/cores/arduino/Print.h error: overriding 'virtual size_t Print::write(uint8_t)' |
|
#1960000 |
|
Посетитель
|
|
Как проверить жк диспей самым простым скетчем при схеме подключения из первого поста? |
|
#1960001 |
Постоянный посетитель
|
|
Bwzs Да, это самый простой скетч для проверки платы без какой либо периферии. Ну и результат означает, что у Вас работоспособная плата. Bwzs Всего скорее, эта библиотека не подойдет к вашей среде программирования. На Платформах Windows и Linux в среде программирования ARDUINO - используется компилятор имеющий одного предка, а на MacOS - другой компилятор, который может некоторые определения не понимать. Что и происходит всего скорее. Я бы конечно, в любом случае - адаптировал библиотеку, или написал бы свою. А так, даже не знаю что Вам сказать, я не могу ответить точнее, не имея данную платформу на руках. Можно конечно, под MacOS использовать более продвинутый и сложный продукт для программирования AVR процессоров - Atmel Studio. Компилятор в этом продукте всего скорее эту библиотеку поймет. Этот продукт можно скачать с сайта ATMEL. Но это, все равно, что "феррари" посоветовать новичку водителю. |
|
#1960050 |
Посетитель
|
|
Немного решил проблему) Поставил на мак винду (боже какая она сложная) ну да ладно. |
|
#1960056 |
Постоянный посетитель
|
|
Bwzs Ну компилятор ругается на отсутствие библиотеки. У Вас файл RTClib.h где располагается? |
|
#1960180 |
|
Посетитель
|
|
с библиотекой разобрался) Теперь проблемы с инициализацией дисплея, |
|
#1960186 |
Посетитель
|
|
Ладно .пока, я так понял, я пришел не тот форум по этом теме) пойду на специализурующий форум по ардуино. Жаль только что покупал детали из первого поста и качал библиотеки из первого, и не работает ничего |
|
#1960193 |
Постоянный посетитель
|
|
сообщение Bwzs Да не разобрались Вы...если бы разобрались то Вот этих проблем не было бы сообщение Bwzs Эта строчка - вызывает функцию, так называемый "конструктор" - которая создает объект класса LiquidCrystal_I2C. Этот класс объявлен в вышеуказанной библиотеке. Если по каким-то причинам компилятор не может получит доступ к библиотечным файлам, то при компиляции этой строки - он просто не поймет о чем идет речь в тексте кода, и соответственно выдаст ошибку, о том, что данный класс ему не известен. Потом, если в библиотечных файлах компилятор встретит переменные таких типов, которые ему не известны - также будет ошибка. Т.е. необходимо изучать ошибки, которые говорят о несовместимости типов, и менять типы переменных, на совместимые. Потом, может и строка инициализации не подойти, т.к. не все дисплеи имеют адрес на I2С шине равный 0х27. Но эта ошибка не на этапе компиляции, т.к. компилятору вообще все равно, какой будет адрес у интерфейсного модуля для дисплея, о в любом случае откомпиллировал бы код. У меня есть скетч, который перебирает адреса от 1 до 255, и когда приобретается дисплей с интерфейсом I2C и неизвестным адресом, то я запускаю этот скетч, и когда адрес совпадет, то дисплей инициализируется и значение адреса выводится на дисплей. сообщение Bwzs Что я Вам могу сказать, я много программировал разные устройства промышленной автоматизации, и среды программирования использовал под разные операционные системы. И могу сказать, что в разных операционных системах, компиляторы, с виду одни и теже - ведут себя по-разному. Прежде всего, это из-за разницы архитектуры. Почему например, компиллеры под Windows и Linux на интеловой платформе - наиболее родственны. Если, Вы взялись программировать по одному известному методу- "метод научного тыка" - т.е. метод случайного подбора кусков кода - то...да...лучше поискать коллег любителей этого дела - но боюсь, много не найдете, ибо на специализированных форумах тусуется более подготовленный народ. То, что я здесь, разжевываю материал как для детей 4-5 класса средней школы - больше никто не делает....да и я это делать скоро прекращу..... Действительно....наверное, не нужно превращать аквариумистический форум в форум по программированию... Изменено 7.4.14 автор Z0RS |
|
#1960229 |
|
Посетитель
|
|
Z0RS Попытки разобраться в программировании с++ не удались успехом хотел собрать код для управления светом и со2 но тут я даже не могу модули подружить, что будет дальше... А вам реально надо медаль вешать, так не расписывают даж на спец форумах. |
|
#1960293 |
Постоянный посетитель
|
|
Bwzs Я вот что подумал.... ну не получается пока с LCD дисплеем - да и не надо. Не используйте его пока...по-позже подключите если понадобится. Основной функционал вообще к LCD не имеет отношения. Я, сам - дисплеи с контроллерами, практически никогда не использую, только иногда - в отладочных целях, чтобы поток вывода не гонять в компорт - тогда организую вывод на LCD. Вообще, на группе предприятий (металлургия) где я много лет работал - тысячи программируемых контроллеров. И сколько из них имеют дисплеи - знаете? НИСКОЛЬКО. Сделать, автоматизацию света, дозаторов и т.д. - можно без дисплея. Главное, одна мелкая проблема - не должна Вас останавливать, тогда Вы точно добьетесь определенного успеха... |
|
#1960455 |
Завсегдатай, Кандидат в Советники
|
|
сообщение Z0RS А-а-а-а-а, только не это!!!1111 Благодаря вашей теме я почти собрал (на макете пока) полностью работающий контроллер. До этого около года засматривался на Arduino, но не решался, т.к. не программист и не математик, а гуманитарий. А тут вы все разжевали, как для тупых (это хорошо!), ну а как положить в рот - это пусть каждый форумчанин решает сам, это не так трудно после ТАКИХ объяснений. ZORS, не бросайте тему, вы еще нужны . |
|
#1961586 |
|
Посетитель
|
|
Дядька-рыбомор Да-да! полностью поддерживаю. |
|
#1961664 |
Постоянный посетитель
|
|
Дядька-рыбомор Да... пока и не собираюсь. |
|
#1961769 |
Посетитель
|
|
А я похвастаюсь дозатором удобрений: Z0RS Я пока тут разобраться хочу, но не могу. Требуется функция аргументом которой будет 1. серва, 2. кол-во мл (а вернее качков - так откалибровано) Проблема в том, что у каждой сервы свои значения крайних положений. for (int i=0; i myservo.writeMicroseconds(1120); delay (1000); myservo.writeMicroseconds(500); delay (1000); } Вот это кусок кода калибровки для 1-й сервы. У второй совсем другие значения: for (int i=0; i myservo.writeMicroseconds(815); delay (1000); myservo.writeMicroseconds(1500); delay (1000); } Планирую возможность расширения до 4 дозаторов, но как это грамотно реализовать? Ардуино двумерные массивы поддерживает? А то в документации не найду никак про это. И наверно от задержек тут отказаться не удастся. И еще вопросик - так как сервы всегда используются по очереди стоит ли городить огород с раздельным питанием? Изменено 13.4.14 автор arch07 |
|
#1962874 |
|
Постоянный посетитель
|
|
arch07 Ну, тогда функция должна принимать эти значения в качестве переменных. И самое интересное, что в любом случае, можно не использовать организацию программного кода в функции. Это аксиома программирования. Ну, это так....теория. arch07 Посмотрите как реализован многокомандный таймер. Есть массив структур, в котором одна "строка" - это одно задание, и полей с различными характеристиками задания - может быть много, очень много, если необходимо. Это у меня в примере характеристик в задании пять штук, но может быть и любое другое количество. Наверное Вам необходимо что-то подобное создать. arch07 Да компилятор поддерживает двумерные массивы, но в данном случае применение их не оправдано. Многомерные массивы, как правило, используются для сложной мат.обработки однородных данных. Тем более, что хранить данные заданий - лучше во флеш-памяти, а в случае, когда данные заданий просто находятся в программном коде, они будут продублированы и во флеш-памяти, и при загрузке программы в RAM - они будут скопированы в оперативку, отнимая ее полезный объем. Конечно, пока Вы не упираетесь в объем доступной RAM - об этом можно не беспокоится.... arch07 Это всегда не просто. Но всегда возможно не использовать функцию delay(). Но это - программирование на уровне очень хорошего знания Си. arch07 Ну это равноценные варианты....и так и эдак, особой сложности при программировании не должно вызывать. |
|
#1962925 |
Посетитель
|
|
Я пока тут разобраться хочу, но не могу. Требуется функция аргументом которой будет 1. серва, 2. кол-во мл (а вернее качков - так откалибровано) Проблема в том, что у каждой сервы свои значения крайних положений. for (int i=0; i |
|
#1962980 Нравится arch07
|
Посетитель
|
|
SilverSwift Спасибо, но я немного не понял принцип. А может понял... Если я хочу хранить переменную count не в структуре, то тогда функция будет вида: void pumpTask(ServoData sData, count){ for (int i=0; i sData.myservo.writeMicroseconds(sData.min); delay (1000); sData.myservo.writeMicroseconds(sData.max); delay (1000); } А вызов для 4-й сервы на 3 качка будет выглядеть так: pumpTask(ServoData4, 3); где ServoData4 - структура для 4-й сервы: struct ServoData4{ int min = 500; int max = 1250; Servo myservo4; }; а в: setup { myservo4.attach(7); //седьмой pin к 4 серве. } Нигде не напутал? p.s. где-то напутал. myservo из функции в структуре нету.... как правильно объявить конкретную серву в структуре, что бы ее использовать в функции? Servo myservo = myservo4; - так? Изменено 16.4.14 автор arch07 |
|
#1964104 |
|
Посетитель
|
|
Если я хочу хранить переменную count не в структуре Чем обосновано такое желание? В большинстве случаев это не рационально. void pumpTask(ServoData sData, count){ for (int i=0; i |
|
#1964233 |
Посетитель
|
|
SilverSwift Ага - понял... теперь все по полочкам разложилось в мозге... А насчет кол-ва качков - собираюсь не хранить их в структуре, потому как структура пишется в проге и описывает конкретное аппаратное устройство. Изменение требуется только при физической замены сервы (ну тогда можно и новую прошивку залить). А кол-во качков (доза удобрений) величина переменная и зависит от многих факторов - света, кол-ва растений, и пр. Я собираюсь делать эти значения настраиваемые и хранить либо на флешке либо энергонезависимой памяти. Хотя при том, что вы описали замена значений внутри структуры не представляет сложности - потому можно и оставить как в вашем варианте. Но мне проще периодичность, время старта и объем подачи удо хранить в другом месте - так мне кажется логичней. И прошу у всех прощения, что подолгу не могу ответить всем, кто мне помогает - весна, строительное обострение. Спать то некогда |
|
#1964275 |
Малёк
46
10 года |
|
Собранный контроллер работает без проблем уже 3-ю неделю. Я всем доволен, единственное есть один нюанс. В установках #define PWM_LW_MAX 120, т.е. убавлен почти наполовину. При выключении света в автоматическом режиме он сначала вспыхивает почти до максимума, а потом начинает как и положено медленно гаснуть. Вопрос: почему появляется вспышка. |
|
#1964352 |
|
Посетитель
|
|
Пока есть время - делается крышка аквариума, сижу прорабатываю концепцию контроллера. |
|
#1965189 |
Посетитель
|
|
Вопрос: почему появляется вспышка. Потому что есть ошибка. Вот кусок кода: Но не в приведенном "куске кода". Пока смотрю в сторону web интерфейса для настроек. Придется впиливать отдельное устройство с поддержкой TCP/IP, чтобы поднять на нем сервер. Я бы реализовал конфигурацию по UART и написал ПО для ПК. Учитывая, что настройки менять придется редко, можно ограничиться конфигурацией по UART через эмулятор терминала типа putty. |
|
#1965354 |
Посетитель
|
|
SilverSwift Отдельное устройство? Реализовать с помощью Ethernet шилд W5100 не получится? Он уже есть В принципе состояние я уже считываю и отображаю. Сейчас разбираюсь как передать со страницы в контроллер. У меня проект не для массового воспроизводства, а "для души" с элементами стимпанка Тут искал интересные решения для автокормушки. Понрвилось: 1. Шнековая кормушка. Массово используется для кормления прудовой рыбы. Но вариант металлического шнека не устраивает (обычно используют сверло), а пластиковый неоткуда взять. Если придумаю как изготовить шнек - реализую. 2. Кормушка типа rondomatic 400, но сложно в изготовлении. Цена родной 20-40 евро отпугивает. Хотя принцип очень нравится. 3. Кормушка на сервомашинке, по принципу переворачиваемой сахарницы. Ограничение корма - трубочкой и прорезью в ней внутри емкости. Очень просто реализуется. Можно дополнить второй машинкой, которая будет открывать крышку аквариума. 4. И вот лидер моего рейтинга, мне прям загорелось. Но боюсь жена такой агрегат в холле не примет категорически. Проблемы пока одни - высокая влажность в квартире, от которой намокает не закрытый корм. И какие-то жучки, которые благополучно пожрали не закрытые таблетки для сомов. Поэтому кормушка должна быть закрыта от атмосферного воздуха да еще и работать с разными видами корма от таблеток до гранул и хлопьев. По этой причине отпали ротационные, так распрстранненые сейчас кормушки. |
|
#1965440 |
|
Посетитель
|
|
Отдельное устройство? Реализовать с помощью Ethernet шилд W5100 не получится? Он уже есть Ардуинский Ethernet шилд - это интерфейс который позволит принимать и отправлять пакетики по Ethernet. При чем сам по себе обмен сожрет большую часть процессорного времени контроллера. Чтобы реализовать управление через веб-морду нужно еще поднять веб-сервер на котором будут крутиться странички и написать cgi-скрипты, которые будут управлять устройством. Во всяком случае такой подход используется на устройствах типа роутеров. Ардуина в принципе не потянет такое ПО. Поэтому нужен софт для конфигурации (с возможностью посмотреть текущие настройки и записать новые) либо через Ethernet, либо через UART. Путь через Ethernet видится избыточным, когда можно тыкнуть шнурок в usb порт ноута и даже через arduino ide читать и писать в UART. Сам примеряюсь к реализации своего "аквариумного контроллера" - специальность программиста обязывает, но вот со свободным временем весьма тухло дела обстоят, так что пока ограничиваюсь обзором чужих решений О кормушках тоже думал, но вариант 3 не встречал 3. Кормушка на сервомашинке, по принципу переворачиваемой сахарницы. Ограничение корма - трубочкой и прорезью в ней внутри емкости. Очень просто реализуется. Можно дополнить второй машинкой, которая будет открывать крышку аквариума. Дадите ссылку? |
|
#1965480 |
Посетитель
|
|
SilverSwift В принципе готовой конструкции тоже не встречал, но это компиляция из ротационной, сахарницы с дозатором и такого механизма: или вот еще пример, но там сама баночка без дозатора, зато реализована доп. крышечка, которая может закрывать отверстие в крышке аквариума: |
|
#1965501 Нравится SilverSwift
|