Implementación de mi sistema domótico

Durante mucho tiempo leí artículos sobre Habré sobre sistemas de automatización del hogar, quería describir en qué he estado trabajando durante más de 2 años. Para comprender mejor mi situación, se necesita una pequeña introducción.

Hace tres años, mi familia y yo nos mudamos a un nuevo apartamento de tres habitaciones (67.5 metros cuadrados), aunque técnicamente el apartamento es, por supuesto, viejo: Stalin, una casa construida en 1946. Cableado de aluminio de dos hilos con trozos de cable trenzado de cobre de 1 m2 en algunos lugares. La revisión estaba por delante, decidí hacer todo yo mismo y comencé con un reemplazo completo del cableado. Compraron 700m de cable de alimentación para iluminación y enchufes de 1.5 y 2.5 Mm cuadrados, bahía de par trenzado, un poco de cable coaxial para antenas de televisión (por si acaso). ¿Por qué tanto y por qué? Pido un gato.

Decidí hacer el cableado de inmediato, es decir: sin cajas de distribución, desde cada punto el cable va al blindaje (con la excepción de las salidas, que pueden estar en un grupo de 2-3 puntos, el cable va al blindaje desde el extremo, el resto está conectado por un bucle) - t. mi. de cada interruptor, su propio cable al escudo, desde cada punto de iluminación, su propio cable al escudo, cerca de los enchufes a lo largo de un par de puntos rj-45, por si acaso. Por supuesto, hay muchos cables. En algún lugar se coloca en el piso de conformidad con todas las reglas del PUE, como, por ejemplo, en la guardería:



En algún lugar, en el techo, por ejemplo, la vista de la puerta del dormitorio:



Como resultado, obtenemos todos los cables en el corredor en un lugar donde se ubicará el escudo, y se pueden cambiar entre ellos como lo desee. Incluso si tiene que cambiar el esquema de conexión en el futuro, no tomará mucho tiempo y no tendrá que estropear la reparación. Y, por supuesto, se hicieron muchos enchufes para todas las ocasiones, en total unos 90 puntos para todo el apartamento.

Así es como se veía el "escudo" temporal durante los experimentos: una



visión terrible, pero todo funcionó y de esta forma este monstruo vivió durante varios meses. Un buen día, las estrellas se formaron con éxito y el escudo se rehizo por decisión deliberada. Tomó 3 días en una escalera destartalada debajo del techo (y los techos en el stalin 3m), pero valió la pena. Así es como comenzó a verse la visera después de eso:



Y la visión general:



Esto está lejos de la forma final del escudo, todavía no hay suficiente RCD, hay 3 veces menos interruptores de circuito de los necesarios, no hay relés de pulso, pero hay líneas sin desconexión, todos los cables están conectados a los terminales, hay al menos algún tipo de selectividad de interruptor de circuito.

Ahora que tiene una idea aproximada de exactamente con qué tuve que trabajar, puede comenzar a describir los "cerebros" del sistema. Sin más preámbulos, tomé la base de arduino, especialmente porque ya tenía 2 placas freeduino, 2 protectores de ethernet y un protector de motor que compré mucho antes. También ordenó a los chinos varios módulos de relé de 4 piezas cada uno. Y también encontré, siguiendo el consejo de uno de los artículos sobre Habré en Ob, interruptores en los que se podían insertar resortes especiales y se convertían en interruptores sin fijación. Es hora de escribir código.

En general, estoy muy conectado con las computadoras en la vida. Terminó la estera de pieles, en la que simplemente no escribió: pascal, c #, c ++, 1c, php, javascript, todavía puede enumerar mucho. La última vez que trabajo en el campo del desarrollo web, también trato con telefonía ip. Por lo tanto, crear un algoritmo es simple, pero con la electrónica "para usted", sé y sé cosas simples, y cuando se trata de algo más complicado: microcontroladores, fusibles, rebote de contacto, es más difícil. Pero no son los dioses los que queman las ollas, los ojos tienen miedo, pero las manos sí. Decidí hacer todo simple. Alimento la tierra desde el arduino a la entrada del interruptor, conecto las salidas del interruptor a los pines analógicos en el arduino (aunque es posible digital, no es importante). Conecto los pines del arduino con los pines del módulo de relé. Técnicamente, todo está listo, cuando presiona el interruptor, el arduino ve el valor BAJO en el pin deseado y establece el BAJO en el pin conectado al módulo de relé.Dado que todos los cables de los puntos del apartamento llegan a las terminales, la conexión no tomó mucho tiempo.
Para combatir el rebote de contactos, después de estudiar el tema en Internet, se seleccionó la biblioteca Bounce2. En general, inicialmente quería escribir un código universal para poder usarlo con al menos 2 interruptores, al menos 22. Fue esta tarea la que formó la base de todo el algoritmo. Bueno, ahora de las palabras al código. No cargaré completamente todo el código; los enlaces al github estarán al final del artículo. Solo mostraré momentos importantes, desde mi punto de vista.

Entonces, declarando bibliotecas y variables:

#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];

El punto es: es suficiente aumentar la constante con el número de lámparas / interruptores y registrar nuevas asociaciones en las matrices, y eso es todo, se agregará una nueva lámpara o interruptor. Al mismo tiempo, la estructura del código permite que un interruptor controle de inmediato varios dispositivos. O varios interruptores para controlar inmediatamente una lámpara. Además, para cada cambio, se crea su propia instancia del objeto Bounce para anti-rebote.

Todos los arreglos se inicializan en la función de configuración, y almacenamos el estado de las luminarias en la memoria no volátil, de modo que cuando se produce un corte de energía o un reinicio, todo se enciende igual que antes de la falla.

  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);
  }

No diré nada sobre el primer ciclo, volveremos sobre él un poco más tarde. Y lo más interesante comienza en el cuerpo del 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 las pruebas de las primeras versiones del algoritmo, se observó que a los niños (y tengo tres de ellos, niños) realmente les gustaba hacer clic en los interruptores. Por lo tanto, era necesario poder apagar algunos interruptores para que el controlador no respondiera a ellos. La opción más obvia es simplemente eliminar los pines necesarios del tablero, pero esto es incorrecto y poco interesante. Por lo tanto, los indicadores también se escriben en la memoria no volátil que indica si el conmutador está abierto o no. Esto se inicializa con este bucle:

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

  ...

Y comprobado aquí:

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

Ya en esta etapa, los interruptores locales en las paredes comenzaron a funcionar. Pero una casa inteligente no sería inteligente sin una interfaz. Como me estoy ocupando del desarrollo web, se decidió crear una interfaz web. Aquí es donde los escudos de Ethernet fueron útiles. Desafortunadamente, no pude encontrar las fuentes de las primeras versiones de los programas que usan escudos ethernet para control remoto. Intentaré buscar copias de seguridad, tal vez estén allí. Pero el significado era primitivo para la desgracia. Cada controlador tiene su propia dirección IP. Un servidor web estaba subiendo en el arduino, que analizaba las solicitudes GET y encendía o apagaba la lámpara correspondiente según el número de puerto. Hay muchos ejemplos de este tipo en Internet. Para la interfaz web, se construyó un servidor en la placa base con Intel Atom incorporado, se instaló Ubuntu Server 14.02, se instaló el conjunto LAMP estándar,allí se escribe una interfaz simple. Todas las fuentes también estarán al final del artículo. Por el momento, se ve así:



Como puede ver, una lámpara de la cocina está encendida y el interruptor responsable está bloqueado. La administración es mega simple: simplemente haga clic en el elemento deseado y sus cambios de estado.

Y todo sería genial, si no fuera por un "pero". Los escudos de Ethernet se colgaban constantemente. No, el control local de los interruptores siempre funcionó como un reloj. Pero el control remoto desde la interfaz web se caía constantemente. Si la gerencia trabajó por un día, fue excelente. Pero más a menudo, el escudo colgaba solo unas horas después del reinicio. Lo que no hice fue tratar de tratar con el perro guardián, pero mis tablas no lo soportaron. Pedí en China y reemplacé los escudos con otros, en en28j60, mejoró, pero aún así colgaban periódicamente. Agregué un módulo de relé al controlador, llevé la energía a las placas arduino a través de contactos normalmente cerrados, y una de las placas arduino tiró del relé con una cierta frecuencia y se cortó la alimentación, y luego se restableció; sin embargo, esto tampoco siempre funcionó y parpadeó en el momento del reinicio luces encendidas,incluso por un par de segundos, pero no obstante. Así es como se veía el controlador en este punto:



Y luego se decidió abandonar por completo los escudos de Ethernet. Empecé a buscar otras funciones de administración remota. Traté de conectar arduins directamente al servidor y enviar comandos a través de Serial.read () / Serial.print (): funcionó en cualquier otro momento, no se logró la estabilidad porque la placa se reiniciaba cada vez que se accedía desde un script en el servidor Leí mucho sobre tales errores, me di cuenta de que estaba conectado con el DTR, en algún lugar escribieron que puede inicializar el puerto con otras banderas, dio ejemplos que no funcionaron para mí. Después de un tiempo, me encontré con un artículo sobre cómo hacer un adaptador USB-I2C de un programador USBAsp. Decidí, ¿por qué no? Pedí un par de tales programadores a los chinos, y esperé.

Y hace una semana llegó mi paquete. Un programador se actualizó con un pequeño firmware USB i2c y nuevamente me senté a reescribir el código. Aquí las características del protocolo comenzaron a aparecer. Por supuesto, el servidor era el maestro, y todas las placas Arduino eran esclavas.

El código debe resolver las siguientes tareas:

- informar el estado del puerto solicitado;
- cambiar el estado del puerto solicitado;
- apague o encienda todas las luces.

Y me encontré con un problema. Puedo usar los comandos estándar del conjunto de herramientas i2c para comunicarme con las placas arduino. Este es un equipo

i2cget -y < > <>

y

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

Parece que, a juzgar por el hombre, podría pasar el valor de la palabra, pero no funcionó para mí, o me equivoqué en alguna parte.

El problema es que, en primer lugar, si necesito cambiar el estado de un determinado puerto, primero debo pasar el número del comando responsable de esta operación y luego el número del puerto. Incluso intenté hacer esto, enviar 2 comandos y en el código para recopilarlos por turno, pero fue feo e irracional.

Problema número dos: Arduino no puede responder algo en el momento en que recibe los datos. Hay dos métodos de la biblioteca Wire: onReceive () cuando se reciben datos del maestro y onRequest () cuando el maestro solicita los datos.

Así que hice esto: el servidor web tiene 6 comandos:

  • comando "1": obtenga el estado de todas las luces
  • comando "2" - obtiene el estado de bloqueo de todos los interruptores
  • comando "5" - enciende el mundo entero
  • comando "6" - apaga el mundo entero

un comando formado de la siguiente manera: un número decimal de la siguiente forma <cientos (1 o 2) - cambie la lámpara o bloquee el bloqueo> <número de puerto (de 0 a 99)>; por ejemplo, 105 - conmutador de 5 puertos, 213 - conmutador de 13 puertos de bloqueo. Este comando se traduce a hexadecimal y se pasa al arduino, que realiza las transformaciones inversas y comprende lo que hay que hacer.

Esto es lo que parece en el lado del 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);
  ...

Y desde el lado de 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, pero funciona. El segundo problema se resuelve de la siguiente manera. Aquí está la función 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;
  }
}

Con los equipos 3-6, todo está claro, pero los equipos 1 o 2 se pueden describir con más detalle. El servidor primero envía el comando deseado, y cuando el arduino recibe el comando 1 o 2, las banderas se inicializan:

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

Y luego el servidor comienza a enviar solicitudes al arduino tantas veces como los puertos que desea sondear. En el lado del 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);

En pocas palabras: hacemos varios intentos ($ tryes> 3) para interrogar al arduino, obtenemos una línea que consta de 0 o 1. En cualquier lugar con cualquier comando miramos la respuesta, si existe la palabra Error, significa que hubo errores durante la transferencia y debe repetir la transferencia. . Se hicieron varios intentos para garantizar la exactitud de la cadena transmitida; la matriz se contrae por cadena utilizando el método array_count_values ​​($ str); y al final obtenemos una matriz con el número de ocurrencias de las mismas líneas, damos la línea que más recibimos de arduino.

Desde el lado de arduino todo es más simple:

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;
  }
}

El código de la página web contiene los 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>

En realidad, indico explícitamente la dirección de la placa a la que necesito acceder y el número de puerto.

Oh sí, el controlador se ve así ahora:



Después de que todo se instaló en su lugar, todo comenzó la primera vez. Y al día siguiente, surgió un efecto incomprensible: si enciende todas las luces de la habitación, toda la luz de la habitación comienza a parpadear cada 2-3 segundos. Pasé toda la noche eligiendo el código; no había tal jamba en el banco de pruebas, por lo que el problema no está en el código. Rebusqué en un montón de foros, en una toga en uno de ellos encontré una descripción de un síntoma similar, verifiqué mi suposición y el problema desapareció. Y lo que pasaba era que alimentaba a los tres arduins desde una fuente de alimentación de computadora, 12V, y el viejo freeduino comía en silencio y no zumbaba, y arduino uno v3 no podía, y cuando todos los relés estaban encendidos, el regulador de potencia se calentaba para que Era imposible tocarlo. Redujo el voltaje de suministro a 5V 2A, y funcionó como debería.

Hay muchos planes, tenemos que terminar la reparación en el apartamento, el pasillo y el baño con el inodoro están alineados, en mis sueños quiero controlar el calentador de agua y cada toma de corriente, ya que ahora puedo colgar cualquier cantidad de arduina en el autobús I2C y cada uno hará lo suyo. También se planea agregar relés de pulso al riel DIN para que los módulos de relé solo controlen estos relés de pulso, y ya toda la carga pasa por este último. Porque existen serias dudas sobre la fiabilidad de los módulos de relés chinos. Pero eso es todo en el futuro.

Según lo prometido, enlaces a github:

interfaz web incompleta
para arduino

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


All Articles