Выпуск №18. Авто жалюзи


Авто жалюзи

статья является копипастом с mysku перейти к источнику. Сам пока не собирал, руки не дошли, но очень хочется, поэтому сохранил у себя с файлами прошивок.

Автоматическое управление жалюзи и интеграция в умный дом

Добрый день уважаемые. Черт дернул написать в здешний форум про свою конструкцию управления горизонтальными металлическими жалюзями. Попросили обзор, ну вот собственно кому интересно прошу пожаловать.


Мысль сделать автоматизацию жалюзей не покидала меня давно. Во-первых извечная лень, во-вторых свет от окошка мешает работать за компом.
Сказано — сделано :)
Вариант автоматизировать путем вращения пластиковой гм… не знаю как это называется отпал сразу — ненадежный механизм.

Поэтому пошел другим путем:
Был приобретен моторчик с редуктором 6V 30 RPM:

вот тут:

так же вот такие сенсоры поворота (резистор 10К) SV01A103AEA01R00


а также вот такие муфты, так как вал у движка 3 мм, в жалюзях 4 мм:


что получилось дальше — смотрим на картинках:




плату блока управления видно на первой картинке. колхоз конечно, но в корпусе не видно :)

из чего все это состоит:
1. ESP8266 (брал в варианте Wemos D1 mini вот тут)
2. I2C АЦП 12-бит 4 канала ADS1115
3. I2C датчик освещенности TSL2561
4. I2C модуль управления двигателями Motor Shield For D1 mini. Жалюзи у меня из двух частей, так что как раз два канала управления пригодятся.

Как все это собирается:
Очень просто, все модули объединяются по I2C, следовательно два провода + питание.

Начнем с моторчика и сенсора в жалюзях. Определим условный плюс мотора — подключаем питание, смотрим в торец вала. Полярность при которой она вращается по часовой отмечаем как + и -.
На сенсор подаем питание таким образом чтобы находясь на валу двигателя он в крайне «левом» против часовой стрелки на среднем выводе выдавал 0. На сенсор подаем 3.3V не 5!, оно есть на выводе платы ESP.
Мотор подключаем:
+ на A1 платы управления двигателем
— на A2
второй мотор по аналогии на B1 и B2.

Обратите внимание на фото — эти полярности для установки привода слева. Если ставим его в правой части, то + и — на моторе, VCC и GND на сенсоре просто меняем местами.

С платой двигателя есть засада — он придет или непрошитый (там внутри свой процессор) или с кривым софтом.
Проблема и решение описаны вот тут:
Ищем «Reprogramming Without Soldering». Для перепрограммирования нужен любой RS232 TTL USB адаптер.
ВАЖНО: на обратной стороне платы потом в блоке отмеченном прямоугольником STBY запаиваем перемычку I2C, иначе работать не будет. Прошивка вот тут.
Поскольку SDA и SLC на это плате выведены на D2 — SDA, D1 — SCL не забываем правильно подключить их на ESP.
Я использовал те же линии:
GPIO 4 — D2 — I2C — SDA
GPIO 5 — D1 — I2C — SCL
На VM и GND с торца платы подаем питание для моторов: VM: + 5V, GND тоже понятно.
Питание проца платы 3.3V берем с одноименных выводов на плате ESP. (3V3 и GND).

Подключение TSL2561 просто +3.3V, GND, SDA, SLC
Подключение ЦАП ADS1115 аналогично. На A0 подаем средний вывод первого сенсора, на A1 — второго.

Теперь самое главное и страшное — ESP8266.
Для прошивки использовал проект Макса WiFi IOT.
Прошивка платная — 2.5$ за модуль. Но оно стоит того. В плюсах куча поддерживаемого железа, обновление прошивки во воздуху, возможность кастомизации путем инжекции своего кода на C (именно это тут нам и нужно), поддержка протокола MQTT.
Ну и коммюнити на сайте, всегда можно поплакать в жилетку :)
Для прошивки выбираем компоненты:
TSL2561
MQTT клиент
ADS1115
NTP Sync
I2C via GET
I2C Scanner
GPIO
OTA Update
Auto OTA
Code designer
Initial settings

Две последние опции нуждаются в кастомизации:
Initial settings — прописываем SSID и пароль для Wi-Fi, статический IP
Code designer:
Копипастим код:
// global variables
// valdes[0] = 0 - manual, 1 - auto
// valdes[1] = position 0 - 100
// valdes[2] = 0 - manual, 1 - auto - 2 channel
// valdes[3] = position 0 - 100 - 2 channel

static bool motFreqSet = false;
static int falseStep = 0;
static int prevPos1 = -1;
static int prevPos2 = -1;
static int targetAdc[2] = {0,0};
static int leftCycles = 0;
static int rightCycles = 0;
static int direction[2] = {0,0};
static int lastLux = 0;

//static os_timer_t esp_timer[0]; // define timer esp_timer[0]
static os_timer_t esp_timer[2]; // define timer esp_timer array


void ICACHE_FLASH_ATTR setMotFreq()
{
//if(motFreqSet) 
//    return;
i2c_start();
i2c_writeByte(0x60);
    if(i2c_getAck()){falseStep = 1; i2c_stop(); return;}
i2c_writeByte(0x00);
    if(i2c_getAck()){falseStep = 2; i2c_stop(); return;}
i2c_writeByte(0x00);
    if(i2c_getAck()){falseStep = 3; i2c_stop(); return;}
i2c_writeByte(0x3e);
    if(i2c_getAck()){falseStep = 4; i2c_stop(); return;}
i2c_writeByte(0x80);
    if(i2c_getAck()){falseStep = 5; i2c_stop(); return;}
i2c_stop();
motFreqSet = true;
os_delay_us(1000);
}


void ICACHE_FLASH_ATTR motorLeft(int motor)
{
setMotFreq();
//falseStep =0;
i2c_start();
i2c_writeByte(0x60);
    if(i2c_getAck()){falseStep = 6; return;}
if(motor == 1)
    i2c_writeByte(0x10);
else 
    i2c_writeByte(0x11);
    if(i2c_getAck()){falseStep = 7; return;}
i2c_writeByte(0x01);
    if(i2c_getAck()){falseStep = 8; return;}
i2c_writeByte(0x01);
    if(i2c_getAck()){falseStep = 9; return;}
i2c_writeByte(0x00);
    if(i2c_getAck()){falseStep = 10; return;}
i2c_stop();

}

void ICACHE_FLASH_ATTR motorRight(int motor)
{
setMotFreq();
//falseStep =0;
i2c_start();
i2c_writeByte(0x60);
    if(i2c_getAck()){falseStep = 6; return;}
if(motor == 1)
    i2c_writeByte(0x10);
else 
    i2c_writeByte(0x11);
    if(i2c_getAck()){falseStep = 7; return;}
i2c_writeByte(0x02);
    if(i2c_getAck()){falseStep = 8; return;}
i2c_writeByte(0x01);
    if(i2c_getAck()){falseStep = 9; return;}
i2c_writeByte(0x00);
    if(i2c_getAck()){falseStep = 10; return;}
i2c_stop();

}

void ICACHE_FLASH_ATTR motorStop(int motor)
{
setMotFreq();
//falseStep =0;
i2c_start();
i2c_writeByte(0x60);
    if(i2c_getAck()){falseStep = 6; return;}
if(motor == 1)
    i2c_writeByte(0x10);
else 
    i2c_writeByte(0x11);
    if(i2c_getAck()){falseStep = 7; return;}
i2c_writeByte(0x03);
    if(i2c_getAck()){falseStep = 8; return;}
i2c_writeByte(0x01);
    if(i2c_getAck()){falseStep = 9; return;}
i2c_writeByte(0x00);
    if(i2c_getAck()){falseStep = 10; return;}
i2c_stop();

}

void ICACHE_FLASH_ATTR motor_proc(unsigned char motor){
// arg = motor number
//int targetAdc[0] = arg;
int curAdc = readADC_ADS(motor - 1);

// check if rotate to desired position
if(direction[motor - 1] == 1)
{
    // rotating right
    rightCycles++;
    if ( curAdc >= targetAdc[motor - 1]) {motorStop(motor); os_timer_disarm(&esp_timer[motor - 1]);}
}
else
{
    // rotating left
    leftCycles++;
    if ( curAdc <= targetAdc[motor - 1]) {motorStop(motor); os_timer_disarm(&esp_timer[motor - 1]);}
}

}

void ICACHE_FLASH_ATTR
startfunc(){
    // выполняется один раз при старте модуля.
    valdes[0] = 1;
    valdes[2] = 1;
}

void ICACHE_FLASH_ATTR
 timerfunc(uint32_t  timersrc) {
// выполнение кода каждую 1 секунду

// check channel 1 for auto mode
if(valdes[0] == 1)
{
    // auto mode
    // calculate target position
    
    if(tsllux <= sensors_param.cfgdes[4]/* luxNight */)
    {
        // night time, full closed DOWN
       valdes[1] = 0;
    }
    else 
        if(tsllux <= sensors_param.cfgdes[3]/* luxOpened */)
        {      
            // more then dark, full opened
            valdes[1] = 50;
        }
        else 
            if(tsllux >= sensors_param.cfgdes[2]/* luxClosed */) 
            {
                // very light, full close UP
//                valdes[1] = 100;
                valdes[1] = 0;
            }
            else
            {
                // calculate desired position
                valdes[1] = (sensors_param.cfgdes[2]/* luxClosed */ - tsllux)*50/(sensors_param.cfgdes[2]/* luxClosed */ - sensors_param.cfgdes[3]/* luxOpened */);
            }

}

// check channel 2 for auto mode
if(valdes[2] == 1)
{
    // auto mode
    // calculate target position
    
    if(tsllux <= sensors_param.cfgdes[4]/* luxNight */)
    {
        // night time, full closed DOWN
       valdes[3] = 0;
    }
    else 
        if(tsllux <= sensors_param.cfgdes[3]/* luxOpened */)
        {      
            // more then dark, full opened
            valdes[3] = 50;
        }
        else 
            if(tsllux >= sensors_param.cfgdes[2]/* luxClosed */) 
            {
                // very light, full close UP
//                valdes[1] = 100;
                valdes[3] = 0;
            }
            else
            {
                // calculate desired position
                valdes[3] = (sensors_param.cfgdes[2]/* luxClosed */ - tsllux)*50/(sensors_param.cfgdes[2]/* luxClosed */ - sensors_param.cfgdes[3]/* luxOpened */);
            }

}

int curAdc;

// check if new position isn't equivalent to previous for channel 1
if(prevPos1 != valdes[1]) 
{
    prevPos1 = valdes[1];


    // get direction[0] and required sensor postion
    curAdc = readADC_ADS(0);
    targetAdc[0] = (sensors_param.cfgdes[1] - sensors_param.cfgdes[0])*valdes[1]/100 + sensors_param.cfgdes[0];
    direction[0] = 0 ;// 1 - right, 0 - left
    if(targetAdc[0] < curAdc) 
    {
        direction[0] = 0;
        motorLeft(1);
    }
    else
    {
    direction[0] = 1;
    motorRight(1);
    }

    // start timer to check position
    os_timer_disarm(&esp_timer[0]);
    os_timer_setfn(&esp_timer[0], (os_timer_func_t *)motor_proc, 1); 
    os_timer_arm(&esp_timer[0], 50, 1);
}

// check if new position isn't equivalent to previous for channel 2
if(prevPos2 != valdes[3]) 
{
    prevPos2 = valdes[3];


    // get direction[0] and required sensor postion
    curAdc = readADC_ADS(1);
    targetAdc[1] = (sensors_param.cfgdes[6] - sensors_param.cfgdes[5])*valdes[3]/100 + sensors_param.cfgdes[5];
    direction[1] = 0 ;// 1 - right, 0 - left
    if(targetAdc[1] < curAdc) 
    {
        direction[1] = 0;
        motorLeft(2);
    }
    else
    {
    direction[1] = 1;
    motorRight(2);
    }

    // start timer to check position
    os_timer_disarm(&esp_timer[1]);
    os_timer_setfn(&esp_timer[1], (os_timer_func_t *)motor_proc, 2); 
    os_timer_arm(&esp_timer[1], 50, 1);
}

if(timersrc%30==0){
// выполнение кода каждые 30 секунд

}
}

void webfunc(char *pbuf) {
os_sprintf(HTTPBUFF,"
Global Values
"); // вывод данных на главной модуля
}


Глобальные переменные: 5
Количество настроек: adcLeft1,adcRight1,luxClosed,luxOpened,luxNight,adcLeft2,adcRight2
Имя вкладки настроек: Blind options

Сохраняем, компилим (я использую версию SDK 2..0.0, выбираем в верхней части).
Заливаем в ESP через USB шнурок и софтом: FLASH_DOWNLOAD_TOOLS_V3.3.6_Win
Флешки на платах ESP идут разные, так что если не прошьется — меняем SPI MODE на DOUT.

Ресетим ESP микриком — идем к компу, вводим ip_address
Попадаем на главную страницу прошивки. Покупаем лицензию, переводим прошивку в Pro Mode.
Конфигурим имя, часовой пояс, настройки на MQTT сервер (о нем позже).

На закладке HardWare конфигурим:
Enable ADS1115
Enable TSL2561
I2C GPIO:
SDA: 4, SCL: 5

На закладке I2C Scanner должны увидеть:

I2C Scanner tester:
0x00 / 0x00 (8 bits) — какой-то глюк в прошивке платы контроллера мотора :)
0x30 / 0x60 (8 bits) — плата контроллера мотора
0x39 / 0x72 (8 bits) — это TSL2661
0x48 / 0x90 (8 bits) это ADS1115

Если все так, то хорошо, иначе ищем косяки с подключением I2C линий или питания.

На закладке Blind Options настраиваем:
adcLeft1: показания сенсора в положении полностью закрытые жалюзи
adcRigt1: показания сенсора в противоположном состоянии жалюзей -пластинки вверх

left минимум 0, но менее 1000 ставить нельзя, так как из-за инерции измерений мотор может проскочить 0
right максимум 18500, ставим меньше на 1000 по этой же причине

для второго сенсора аналогично.
luxClosed: освещенность при которой жалюзи полностью закрыты (у меня 15000)
luxOpened: освещенность при которой жалюзи полностью открыты, пластины горизонтально (у меня 7000)
luxNight: освещенность ниже это приведет к полному закрытию (чтобы но ночам в окна не заглядывали :)

между luxClosed и luxOpened будет автоматически регулироваться между 0(закрыто) и 50(открыто).

В собранном виде устройство выглядит вот так: (красным выделено место установки датчика освещеннности).



По умолчанию включается режим автоматики, ручной возможен через MQTT и умный дом. Примерно вот так: (кусок картинки)


О чем я расскажу во второй части, если первая будет хоть кому-то интересна :)
Ну и готов ответить на любые вопросы и добавить необходимую информацию.

Все спасибо :)

Файлы для скачивания:

Скачать motor_shield.zip

Наверх


Добавить комментарий (через VK):

Добавить комментарий к статье могут только зарегистрированные пользователи: