Minha implementação do sistema de automação residencial

Durante muito tempo, li artigos sobre Habré sobre sistemas de automação residencial, queria descrever o que trabalho há mais de 2 anos. Para uma melhor compreensão da minha situação, você precisa fazer uma pequena introdução.

Há três anos, minha família e eu nos mudamos para um novo apartamento de três quartos (67,5 m²), embora tecnicamente o apartamento seja obviamente antigo - Stalin, uma casa construída em 1946. Fiação de dois fios de alumínio com pedaços de cabo de cobre de 1 metro quadrado em alguns lugares. A revisão estava à frente, eu decidi fazer tudo sozinho e comecei com uma substituição completa da fiação. Foram comprados 700m de cabo de alimentação para iluminação e soquetes de 1,5 e 2,5 m², compartimento de par trançado, um pouco coaxial para antenas de televisão (apenas no caso). Por que tanto e o que aconteceu - peço um gato.

Decidi fazer a fiação imediatamente, a saber: sem caixas de distribuição, de cada ponto o cabo vai para a blindagem (com exceção das tomadas, que podem estar em um grupo de 2-3 pontos, o cabo vai para a blindagem ao extremo, o restante é conectado por um loop) - t. e de cada comutador, seu próprio cabo para a blindagem, de cada ponto de iluminação, seu próprio cabo para a blindagem, próximo a soquetes ao longo de um par de pontos rj-45, por precaução. Claro, existem muitos cabos. Em algum lugar, é colocado no chão, em conformidade com todas as regras da PUE, como, por exemplo, no berçário:



Em algum lugar - no teto, por exemplo, vista da porta do quarto:



Como resultado, obtemos todos os cabos no corredor em um local onde a blindagem estará localizada, e eles podem ser trocados entre si conforme desejado. Mesmo se você precisar alterar o esquema de conexão no futuro, não demorará muito tempo e não precisará estragar o reparo. E, é claro, muitas tomadas foram feitas para todas as ocasiões - no total, cerca de 90 pontos para todo o apartamento.

Era assim que o "escudo" temporário era durante as experiências: uma



visão terrível, mas tudo funcionou e, dessa forma, esse monstro viveu por vários meses. Um belo dia, as estrelas foram formadas com sucesso e o escudo foi refeito por decisão deliberada. Demorou 3 dias em uma escada precária sob o teto (e os tetos no stalin 3m), mas valeu a pena. Aqui está a aparência do visor depois disso:



E a visão geral:



Isso está longe da forma final da blindagem, ainda não há RCD suficiente, há 3 vezes menos disjuntores do que o necessário, não há relés de pulso - mas há linhas sem desconexão, todos os cabos estão conectados aos terminais, há pelo menos algum tipo de seletividade de disjuntor.

Agora que você tem uma idéia aproximada do que exatamente eu tinha para trabalhar, você pode começar a descrever os "cérebros" do sistema. Sem mais delongas, tomei o arduino como base, principalmente porque já tinha duas placas freeduino, duas blindagens ethernet e uma blindagem de motor que comprei muito antes disso. Também encomendados aos chineses vários módulos de relé de 4 peças cada. E também descobri, por recomendação de um dos artigos sobre Habré em Ob, interruptores nos quais podiam ser inseridas molas especiais e elas se transformaram em interruptores sem fixação. É hora de escrever código.

Em geral, estou intimamente conectado com computadores na vida. Ele terminou o tapete de peles, no qual simplesmente não escreveu - pascal, c #, c ++, 1c, php, javascript - você ainda pode listar muito. Na última vez em que trabalho na área de desenvolvimento web, também trato de telefonia IP. Portanto, criar um algoritmo é simples, mas com a eletrônica "para você", eu sei e sei coisas simples, e quando se trata de algo mais complicado - microcontroladores, fusíveis, rejeição por contato - é mais difícil. Mas não são os deuses que queimam as panelas, os olhos têm medo, mas as mãos o fazem. Eu decidi simplificar tudo. Alimento a terra do arduino à entrada do switch, conecto as saídas do switch aos pinos analógicos no arduino (embora seja possível digital, isso não é importante). Eu conecto os pinos do arduino com os pinos do módulo de relé. Tecnicamente, tudo está pronto, quando você pressiona o interruptor, o arduino vê o valor LOW no pino desejado e define o LOW no pino conectado ao módulo de relé.Dado que todos os cabos dos pontos do apartamento chegam aos terminais - a conexão não demorou muito tempo.
Para combater a rejeição de contatos, depois de estudar o tópico na Internet, a biblioteca Bounce2 foi selecionada. Em geral, inicialmente eu queria escrever um código universal para que pudesse ser usado com pelo menos 2 switches, pelo menos 22. Foi essa tarefa que formou a base de todo o algoritmo. Bem, agora das palavras ao código. Não carregarei completamente todo o código; os links para o github estarão no final do artigo. Mostrarei apenas momentos importantes, do meu ponto de vista.

Então, declarando bibliotecas e variáveis:

#include <Bounce2.h>
#include <EEPROM.h>

const byte cnt = 8;
const byte pin_cnt = 19;

int pins[] = {11,12,13,13,14,15,16,17};
int leds[] = {6, 3, 4, 5, 3, 4, 5, 3};

byte init_leds[cnt] ;
byte init_buttons[cnt];
int button_states[cnt];
Bounce debouncers[cnt];
unsigned long buttonPressTimeStamps[cnt];
boolean changed[cnt];

O ponto é: basta aumentar a constante com o número de lâmpadas / interruptores e registrar novas associações nas matrizes - e isso é tudo, uma nova lâmpada ou comutador será adicionado. Ao mesmo tempo, a estrutura de código permite que um switch controle vários equipamentos ao mesmo tempo. Ou vários interruptores para controlar imediatamente uma lâmpada. Além disso, para cada opção, sua própria instância do objeto Bounce é criada para anti-bounce.

Na função de configuração, todas as matrizes são inicializadas, enquanto o estado das luminárias é armazenado na memória não volátil, de modo que, quando ocorre uma falha de energia ou reinicialização, tudo gira da mesma forma que antes da falha.

  for(byte i=0; i<cnt; i=i+1) {
    EEPROM.write(pins[i], 10);
  }

  for(byte i=0; i<cnt; i=i+1) {
    button_states[i] = 0;
    
    byte value = EEPROM.read(leds[i]);
    if(value==11) {
      init_leds[i] = LOW ;
    }else{
      init_leds[i] = HIGH ;
    }
    init_buttons[i] = HIGH;
    buttonPressTimeStamps[i] = 0;
    changed[i] = false;

    debouncers[i] = Bounce();

    pinMode(pins[i], INPUT);
    pinMode(leds[i], OUTPUT);
    
    digitalWrite(pins[i], init_buttons[i]);
    digitalWrite(leds[i], init_leds[i]);
    
    debouncers[i].attach( pins[i] );
    debouncers[i].interval(5);
  }

Não vou dizer nada sobre o primeiro ciclo, voltaremos a ele um pouco mais tarde. E o mais interessante começa no corpo do ciclo principal.

void loop(){
  for(byte i=0; i<cnt; i=i+1){
    byte dvalue = EEPROM.read(pins[i]);
    if(dvalue!=11) {
      changed[i] = debouncers[i].update();
      
      if ( changed[i] ) {
        int value = debouncers[i].read();
        if ( value == HIGH) {
         button_states[i] = 0;   
        } else {
           if (i > 0 and pins[i] == pins[i-1]) {
             byte prev_value = EEPROM.read(leds[i-1]);
                            
             if(prev_value == 11) {
               digitalWrite(leds[i], LOW );
               EEPROM.write(leds[i], 11);
             }else{
               digitalWrite(leds[i], HIGH);
               EEPROM.write(leds[i], 10);
             }
           } else {               
             byte value = EEPROM.read(leds[i]);
             if(value==11) {
               digitalWrite(leds[i], HIGH );
               EEPROM.write(leds[i], 10);
             }else{
               digitalWrite(leds[i], LOW);
               EEPROM.write(leds[i], 11);
             }
           }
                 
           button_states[i] = 1;
           buttonPressTimeStamps[i] = millis();     
        }
      }
   
      if ( button_states[i] == 1 ) {
        if ( millis() - buttonPressTimeStamps[i] >= 200 ) {
            button_states[i] = 2;
        }
      }
    }
  }

  delay( 10 );
} 

Durante os testes das primeiras versões do algoritmo, observou-se que as crianças (e eu tenho três delas, meninos) realmente gostavam de clicar nos comutadores. Portanto, era necessário poder desligar alguns comutadores para que o controlador não respondesse a eles. A opção mais óbvia é simplesmente remover os pinos necessários da placa, mas isso é errado e desinteressante. Portanto, os sinalizadores também são gravados na memória não volátil, indicando se o comutador está aberto ou não. Isso é inicializado com este loop:

  for(byte i=0; i<cnt; i=i+1) {
    EEPROM.write(pins[i], 10);
  }

  ...

E verificado aqui:

  for(byte i=0; i<cnt; i=i+1){
    byte dvalue = EEPROM.read(pins[i]);
    if(dvalue!=11) {
    ...

Já nesta fase, interruptores locais nas paredes começaram a funcionar. Mas uma casa inteligente não seria inteligente sem uma interface. Como estou lidando com desenvolvimento web, foi decidido fazer uma interface web. É aqui que os escudos ethernet foram úteis. Infelizmente, não consegui encontrar as fontes das primeiras versões dos programas que usam blindagens ethernet para controle remoto. Vou tentar procurar em backups, talvez eles estejam lá. Mas o significado era primitivo para a desgraça. Cada controlador possui seu próprio endereço IP. Um servidor web estava subindo no arduino, que analisava solicitações GET e ligava ou desligava a lâmpada correspondente, dependendo do número da porta. Existem muitos exemplos desse tipo na Internet. Para a interface da web, um servidor foi construído na placa-mãe com o Intel Atom embutido, o Ubuntu Server 14.02 foi instalado, o conjunto LAMP padrão foi instalado,uma interface simples é escrita lá. Todas as fontes também estarão no final do artigo. No momento, fica assim:



Como você pode ver, uma lâmpada da cozinha está acesa e o interruptor responsável por ela está bloqueado. O gerenciamento é mega-simples - basta clicar no item desejado e seu estado muda.

E tudo seria ótimo, se não fosse um "mas". Escudos Ethernet constantemente pendurados. Não, o controle local dos switches sempre funcionava como um relógio. Mas o controle remoto da interface da Web caiu constantemente. Se a gerência trabalhou por um dia, foi excelente. Porém, mais frequentemente, o escudo pendia apenas algumas horas após a reinicialização. O que eu simplesmente não fiz foi tentar lidar com o cão de guarda, mas minhas pranchas não o apoiaram. Encomendei na China e substituí os escudos por outros - na en28j60 - ficou melhor, mas ainda assim eles desligavam periodicamente. Adicionei um módulo de relé ao controlador, trouxe a energia para as placas do arduino através de contatos normalmente fechados, e uma das placas do arduino puxou o relé com uma certa frequência e a energia foi cortada e depois restaurada - no entanto, isso nem sempre funcionou e piscou no momento da reinicialização luzes ligadas,mesmo por alguns segundos, mas mesmo assim. Aqui está a aparência do controlador neste momento:



E então foi decidido abandonar completamente os escudos ethernet. Comecei a procurar outros recursos de gerenciamento remoto. Tentei conectar arduins diretamente ao servidor e enviar comandos via Serial.read () / Serial.print () - funcionava todas as vezes, a estabilidade não era alcançada porque a placa era reiniciada toda vez que era acessada a partir de um script no servidor. Eu li muito sobre esses erros, acabei de perceber que estava conectado com o DTR, em algum lugar eles escreveram que você pode inicializar a porta com outros sinalizadores, deu exemplos que não funcionaram para mim. Depois de um tempo, deparei-me com um artigo sobre como fazer um adaptador USB-I2C a partir de um programador USBAsp. Eu decidi - por que não? Encomendei alguns desses programadores aos chineses - e esperei.

E uma semana atrás, meu pacote chegou. Um programador recebeu um flash com o minúsculo firmware usb i2c e novamente me sentei para reescrever o código. Aqui os recursos do protocolo começaram a aparecer. Obviamente, o servidor era o mestre e todas as placas do Arduino eram escravas.

O código deve resolver as seguintes tarefas:

- relatar o status da porta solicitada;
- mudar o status da porta solicitada;
- desligue ou ligue todas as luzes.

E eu tive um problema. Eu posso usar os comandos padrão do conjunto i2c-tools para me comunicar com as placas do arduino. Esta é uma equipe

i2cget -y < > <>

e

i2cset -y < > <> 0x00 <byte >

Parece que, a julgar pelos homens, você poderia passar a palavra valor, mas não funcionou para mim, ou eu estava enganado em algum lugar.

O problema é que, primeiro, se eu precisar mudar o estado de uma determinada porta, primeiro devo passar o número do comando responsável por esta operação e, em seguida, o número da porta. Eu até tentei fazer isso, envie 2 comandos e no código para coletá-los por sua vez - mas era feio e irracional.

Problema número dois - o Arduino não pode responder algo no momento em que recebe os dados. Existem dois métodos da biblioteca Wire - onReceive () quando dados são recebidos do mestre e onRequest () quando dados são solicitados pelo mestre.

Então eu fiz isso: o servidor web tem 6 comandos:

  • comando "1" - obtém o status de todas as luzes
  • comando "2" - obtém o estado de bloqueio de todos os comutadores
  • comando "5" - liga o mundo inteiro
  • comando "6" - desligue o mundo inteiro

um comando formado da seguinte forma: um número decimal da seguinte forma <centenas (1 ou 2) - alternar a lâmpada ou a trava de comutação> <número da porta (de 0 a 99)>; por exemplo, porta 105 de 5 comutadores, bloqueio de porta 213 - 13. Este comando é traduzido em hexadecimal e passado para o arduino, que executa as transformações inversas e entende o que precisa ser feito.

Aqui está o que parece no lado do servidor:

  ...
  if ($action == 3)
     $val_hex = dechex(intval($port) + 100);
  else
     $val_hex = dechex(intval($port) + 200);
  
  exec("sudo i2cset -y 7 $addr 0x00 0x$val_hex", $output);
  ...

E do lado do arduino:

void receiveEvent(int howMany) {
  byte bytes = Wire.available();
  int x = 0;
  for (byte i=1; i <= bytes; i=i+1) {
    x = Wire.read();
  }
  
  if (x == 1 or x == 2 or x == 5 or x == 6) {
    do_action(x, 0);
  } else {
    if ( x > 200) {
      do_action (4, x - 200);
    } else {
      do_action (3, x - 100);
    }
  }  
}

Parece bastante primitivo, mas funciona. O segundo problema é resolvido da seguinte maneira. Aqui está a função do_action:

void do_action(byte command, byte port) {
  byte value = 0;
  byte dvalue = 0;
  switch (command) {
    case 1:
      start_request = true;
      request_type = 1;
      current_port = 0;
      
      break;
    case 2:
      start_request = true;
      request_type = 2;
      current_port = 0;
      
      break;
    case 3:
      value = EEPROM.read(port);
      if(value==11) {
        digitalWrite(port, HIGH);
        EEPROM.write(port, 10);
      } else {
        digitalWrite(port, LOW);
        EEPROM.write(port, 11);
      }
      
      break;
    case 4:
      dvalue = EEPROM.read(port);
      if(dvalue==11) {
        EEPROM.write(port, 10);
      } else {
        EEPROM.write(port, 11);
      }
      
      break;
    case 5:
      for (byte i=0; i<cnt; i = i + 1) {
        digitalWrite(leds[i], LOW);
        EEPROM.write(leds[i], 11);
      }
      
      break;
    case 6:
      for (byte i=0; i<cnt; i = i + 1) {
        digitalWrite(leds[i], HIGH);
        EEPROM.write(leds[i], 10);
      }
      
      break;
    default:
      
    break;
  }
}

Nas equipes de 3 a 6, tudo fica claro, mas as equipes 1 ou 2 podem ser descritas em mais detalhes. O servidor envia primeiro o comando desejado e, quando o arduino recebe o comando 1 ou 2, os sinalizadores são inicializados:

  start_request = true;
  request_type = 1;
  current_port = 0;

E então o servidor começa a enviar solicitações ao arduino quantas vezes as portas que deseja pesquisar. No lado do servidor:

function get_data($address, $action, $cnt) {
	exec("sudo i2cset -y 7 $address 0x00 0x0$action", $output);
	$tmp = @$output[0];
	while (strpos($tmp,"rror")!==false) {
		exec("sudo i2cset -y 7 $address 0x00 0x0$action", $output);
		$tmp = @$output[0];
	}
	$str = "";
	for ($i = 1; $i <= $cnt; $i++) {
		exec("sudo i2cget -y 7 $address", $output);
		$tmp = @$output[0];
		while (strpos($tmp,"rror")!==false) {
			exec("sudo i2cget -y 7 $address", $output);
			$tmp = @$output[0];
		}
		if ($tmp) {
			if (strpos($tmp,"1")!==false)
				$str .= "1";
			else
				$str .= "0";
		}
		unset($output);
		unset($tmp);
	}
			
	return $str;
}

$str = array();
$c = 1;
while ($c <= $tryes) {
	$tmp = get_data($addr, $action, $cnt);
	
	if (strlen($tmp) == $cnt)
		$str[] = $tmp;
	
	$c++;
}

$new_array = array_count_values($str);

asort($new_array);

$res = "";
$max = 0;
foreach ($new_array AS $key=>$val) {
	if ($val >= $max) {
		$res = $key;
		$max = $val;
	}
}

return preg_split('//', $res, -1, PREG_SPLIT_NO_EMPTY);

Em poucas palavras - fazemos várias tentativas ($ tryes> 3) para interrogar o arduino, obtemos uma linha composta de 0 ou 1. Em qualquer lugar com qualquer comando, procuramos a resposta, se houver a palavra Erro - significa que houve erros durante a transferência e você precisa repetir a transferência . Várias tentativas foram feitas para garantir a correção da string transmitida; a matriz é recolhida por string usando o método array_count_values ​​($ str); e no final obtemos uma matriz com o número de ocorrências das mesmas linhas, fornecemos a linha que recebemos principalmente do arduino.

Do lado do arduino, tudo é mais simples:

void requestEvent() {
  if (request_type == 1) {
    byte value = EEPROM.read(leds[current_port]);
    if(value==11) {
      Wire.write(1);
    } else {
      Wire.write(0);
    }
    
    current_port = current_port + 1;
  } else if (request_type == 2) {
    byte dvalue = EEPROM.read(pins[current_port]);
    if(dvalue==11) {
      Wire.write(1);
    } else {
      Wire.write(0);
    }

    current_port = current_port + 1;
  }
}

O código da página da web contém os controles:

<a class="lamp living_room" id="lamp0x4d3" rel='0x4d' onclick="lamp_click('0x4d',this.id, 3);" ></a>
<a class="lamp kitchen" id="lamp0x4d4" rel='0x4d' onclick="lamp_click('0x4d',this.id, 4);" ></a>
<a class="lamp children_main" id="lamp0x4d5" rel='0x4d' onclick="lamp_click('0x4d',this.id, 5);" ></a>
<a class="lamp children_second" id="lamp0x4d6" rel='0x4d' onclick="lamp_click('0x4d',this.id, 6);" ></a>
<a class="lamp sleeproom_main" id="lamp0x423" rel='0x42' onclick="lamp_click('0x42',this.id, 3);" ></a>
<a class="lamp sleeproom_lyuda" id="lamp0x424" rel='0x42' onclick="lamp_click('0x42',this.id, 4);" ></a>
<a class="lamp sleeproom_anton" id="lamp0x425" rel='0x42' onclick="lamp_click('0x42',this.id, 5);" ></a>

<a class="button button_living_room" id="button0x4d15" onclick="button_click('0x4d',this.id, 15);" ></a>
<a class="button button_kitchen" id="button0x4d14" onclick="button_click('0x4d',this.id, 14);" ></a>
<a class="button button_children_main" id="button0x4d16" onclick="button_click('0x4d',this.id, 16);" ></a>
<a class="button button_children_second" id="button0x4d17" onclick="button_click('0x4d',this.id, 17);" ></a>
<a class="button button_sleeproom_door1" id="button0x4212" onclick="button_click('0x42',this.id, 12);" ></a>
<a class="button button_sleeproom_door2" id="button0x4213" onclick="button_click('0x42',this.id, 13);" ></a>
<a class="button button_sleeproom_lyuda1" id="button0x4214" onclick="button_click('0x42',this.id, 14);" ></a>
<a class="button button_sleeproom_lyuda2" id="button0x4215" onclick="button_click('0x42',this.id, 15);" ></a>
<a class="button button_sleeproom_anton1" id="button0x4216" onclick="button_click('0x42',this.id, 16);" ></a>
<a class="button button_sleeproom_anton2" id="button0x4217" onclick="button_click('0x42',this.id, 17);" ></a>

Na verdade, indico explicitamente o endereço da placa que preciso acessar e o número da porta.

Ah, sim, o controlador se parece com isso agora:



Depois que tudo foi instalado, tudo foi iniciado pela primeira vez. E no dia seguinte, surgiu um efeito incompreensível - se você acender todas as luzes do quarto, toda a luz do quarto começa a piscar a cada 2-3 segundos. Passei a noite inteira escolhendo o código; não havia um batente na bancada de testes; portanto, o problema não está no código. Vasculhei vários fóruns, em uma toga em um deles encontrei uma descrição de um sintoma semelhante, verifiquei meu palpite - e o problema desapareceu. E o fato é que eu alimentei todos os três arduins de uma fonte de alimentação de computador, 12V, e o velho freeduino comeu silenciosamente e não tocou, e o arduino uno v3 não pôde, e quando todos os relés foram ligados, o regulador de energia foi aquecido para que Era impossível tocar. Reduziu a tensão de alimentação para 5V 2A - e funcionou como deveria.

Existem muitos planos, precisamos terminar o reparo no apartamento, o corredor e o banheiro com o vaso sanitário estão alinhados, nos meus sonhos eu quero controlar o aquecedor de água e cada tomada, já que agora posso pendurar qualquer quantidade de arduin no ônibus I2C e cada um fará sua própria coisa. Também está planejado adicionar relés de pulso ao trilho din, para que os módulos de relé controlem apenas esses relés de pulso, e já toda a carga passa pelo último. Porque existem sérias dúvidas sobre a confiabilidade dos módulos de relés chineses. Mas isso é tudo no futuro.

Conforme prometido, links para o github:

Interface da web
esboçada para arduino

Source: https://habr.com/ru/post/pt386457/


All Articles