Pendant longtemps j'ai lu des articles sur Habré sur les systèmes domotiques, je voulais décrire ce sur quoi je travaille depuis plus de 2 ans. Pour mieux comprendre ma situation, vous devez faire une petite introduction.Il y a trois ans, ma famille et moi avons déménagé dans un nouvel appartement de trois pièces (67,5 m2), bien que techniquement l'appartement soit bien sûr ancien - Staline, une maison construite en 1946. Câblage en aluminium à deux fils avec des morceaux de câble toronné en cuivre de 1 mm² à certains endroits. La révision était en avance, j'ai décidé de tout faire moi-même et j'ai commencé par un remplacement complet du câblage. Ils ont acheté 700 m de câble d'alimentation pour l'éclairage et des prises de 1,5 et 2,5 m², une baie à paire torsadée, un peu de câble coaxial pour les antennes de télévision (juste au cas où). Pourquoi tant et ce qui est arrivé - je demande un chat.J'ai décidé de faire le câblage tout de suite, à savoir: sans boîtiers de distribution, de chaque point le câble va au blindage (à l'exception des prises, qui peuvent être dans un groupe de 2-3 points, le câble va au blindage de l'extrême, les autres sont reliés par une boucle) - t. e. de chaque interrupteur son propre câble au blindage, de chaque point d'éclairage son propre câble au blindage, près des prises le long d'une paire de points rj-45, juste au cas où. Bien sûr, il existe de nombreux câbles. Quelque part, il est posé dans le sol conformément à toutes les règles du PUE, comme, par exemple, dans la pépinière:
Quelque part - au plafond, par exemple, vue de la porte de la chambre:
En conséquence, nous mettons tous les câbles dans le couloir en un seul endroit où le bouclier sera situé, et ils peuvent être commutés entre eux comme vous le souhaitez. Même si vous devez changer le schéma de connexion à l'avenir, cela ne prendra pas beaucoup de temps et vous n'aurez pas à gâcher la réparation. Et bien sûr, de nombreuses prises ont été faites pour toutes les occasions - au total environ 90 points pour tout l'appartement.Voici à quoi ressemblait le «bouclier» temporaire pendant les expériences: un
spectacle terrible, mais tout fonctionnait et sous cette forme ce monstre a vécu plusieurs mois. Un beau jour, les étoiles se sont formées avec succès et le bouclier a été refait par décision volontaire. Cela a pris 3 jours sur un escalier branlant sous le plafond (et les plafonds dans le stalin 3m), mais ça valait le coup. Voici à quoi le tableau de bord a commencé à ressembler après cela:
Et la vue générale:
C'est loin de la forme finale du blindage, il n'y a toujours pas assez de RCD, il y a 3 fois moins de disjoncteurs que nécessaire, il n'y a pas de relais d'impulsion - mais il y a des lignes non déconnectables, tous les câbles sont connectés aux bornes, il y a au moins une sorte de sélectivité du disjoncteur.Maintenant que vous avez une idée approximative de ce avec quoi je devais travailler, vous pouvez commencer à décrire les «cerveaux» du système. Sans plus tarder, j'ai pris Arduino comme base, d'autant plus que j'avais déjà 2 cartes Freeduino, 2 blindages Ethernet et un blindage moteur que j'avais acheté bien avant. Également commandé auprès des Chinois plusieurs modules relais de 4 pièces chacun. Et j'ai aussi trouvé, sur les conseils d'un des articles sur Habré à Ob, des interrupteurs dans lesquels des ressorts spéciaux pouvaient être insérés et ils se transformaient en interrupteurs sans fixation. Il est temps d'écrire du code.En général, je suis étroitement lié aux ordinateurs dans la vie. Il a terminé le tapis de fourrure, sur lequel il n'a tout simplement pas écrit - pascal, c #, c ++, 1c, php, javascript - vous pouvez toujours en énumérer beaucoup. La dernière fois que je travaille dans le domaine du développement web, je m'occupe également de la téléphonie ip. Par conséquent, trouver un algorithme est simple, mais avec l'électronique «pour vous», je sais et je sais des choses simples, et quand il s'agit de quelque chose de plus compliqué - microcontrôleurs, fusibles, rebond de contact - c'est plus difficile. Mais ce ne sont pas les dieux qui brûlent les pots, les yeux ont peur, mais les mains le font. J'ai décidé de tout simplifier. J'alimente la terre de l'arduino à l'entrée de l'interrupteur, je connecte les sorties de l'interrupteur aux broches analogiques de l'arduino (bien que cela soit possible en numérique, ce n'est pas important). Je connecte les broches de l'arduino avec les broches du module relais. Techniquement, tout est prêt, lorsque vous appuyez sur l'interrupteur, l'arduino voit la valeur LOW sur la broche souhaitée et définit la LOW sur la broche connectée au module de relais.Étant donné que tous les câbles des points de l'appartement arrivent aux bornes - la connexion n'a pas pris beaucoup de temps.Pour lutter contre le rebond des contacts, après avoir étudié le sujet sur Internet, la bibliothèque Bounce2 a été sélectionnée. En général, j'ai d'abord voulu écrire un code universel afin qu'il puisse être utilisé avec au moins 2 commutateurs, au moins 22. C'est cette tâche qui a constitué la base de tout l'algorithme. Eh bien, maintenant des mots au code. Je ne téléchargerai pas entièrement tout le code; les liens vers le github seront à la fin de l'article. Je ne montrerai que des moments importants, de mon point de vue.Donc, déclarer des bibliothèques et des variables:
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];
Le point est: il suffit d'augmenter la constante avec le nombre de lampes / interrupteurs et d'enregistrer de nouvelles associations dans les tableaux - et c'est tout, une nouvelle lampe ou interrupteur sera ajouté. En même temps, la structure du code permet à un commutateur de contrôler plusieurs appareils à la fois. Ou plusieurs interrupteurs pour contrôler immédiatement une lampe. De plus, pour chaque commutateur, sa propre instance de l'objet Bounce est créée pour l'anti-rebond.Dans la fonction de configuration, tous les tableaux sont initialisés, tandis que l'état des luminaires est stocké dans une mémoire non volatile, de sorte que lorsqu'une panne de courant ou un redémarrage se produit, tout s'allume de la même manière qu'avant la panne. 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);
}
Je ne dirai rien du premier cycle, nous y reviendrons un peu plus tard. Et le plus intéressant commence dans le corps du cycle 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 );
}
Lors des tests des premières versions de l'algorithme, il a été constaté que les enfants (et j'en ai trois, les garçons) aimaient vraiment cliquer sur les interrupteurs. Par conséquent, il était nécessaire de pouvoir désactiver certains commutateurs afin que le contrôleur ne leur réponde pas. L'option la plus évidente est de simplement retirer les broches nécessaires de la carte, mais c'est faux et sans intérêt. Par conséquent, les drapeaux sont également écrits dans la mémoire non volatile indiquant si le commutateur est ouvert ou non. Ceci est initialisé avec cette boucle: for(byte i=0; i<cnt; i=i+1) {
EEPROM.write(pins[i], 10);
}
...
Et vérifié ici: for(byte i=0
byte dvalue = EEPROM.read(pins[i])
if(dvalue!=11) {
...
Déjà à ce stade, des interrupteurs locaux sur les murs ont commencé à fonctionner. Mais une maison intelligente ne serait pas intelligente sans interface. Comme je m'occupe du développement web, il a été décidé de faire une interface web. C'est là que les blindages Ethernet sont utiles. Malheureusement, je n'ai pas pu trouver les sources des premières versions de programmes qui utilisent des blindages Ethernet pour le contrôle à distance. J'essaierai de chercher dans les sauvegardes, peut-être qu'elles sont là. Mais le sens était primitif à la honte. Chaque contrôleur a sa propre adresse IP. Un serveur Web montait sur l'arduino, qui analysait les demandes GET et allumait ou éteignait la lampe correspondante en fonction du numéro de port. Il existe de nombreux exemples de ce type sur Internet. Pour l'interface Web, un serveur a été construit sur la carte mère avec Intel Atom intégré, Ubuntu Server 14.02 a été installé, l'ensemble LAMP standard a été installé,une interface simple y est écrite. Toutes les sources seront également à la fin de l'article. Pour le moment, cela ressemble à ceci:
Comme vous pouvez le voir, une lampe dans la cuisine est allumée et l'interrupteur responsable est verrouillé. La gestion est méga-simple - cliquez simplement sur l'élément souhaité et son état change.Et tout serait super, sinon pour un «mais». Les blindages Ethernet étaient constamment suspendus. Non, le contrôle local des commutateurs fonctionnait toujours comme une horloge. Mais le contrôle à distance de l'interface Web est tombé en permanence. Si la direction a travaillé pendant une journée, c'était excellent. Mais le plus souvent, le bouclier s'est accroché quelques heures seulement après le redémarrage. Ce que je n’ai pas fait, c’était d’essayer de gérer les chiens de garde, mais mes planches ne l’ont pas soutenu. J'ai commandé en Chine et remplacé les boucliers par d'autres - sur en28j60 - ça s'est amélioré, mais ils ont toujours raccroché périodiquement. J'ai ajouté un module de relais au contrôleur, j'ai alimenté les cartes Arduino par des contacts normalement fermés, et l'une des cartes Arduino a tiré le relais avec une certaine fréquence et l'alimentation a été coupée, puis il a été rétabli - cependant, cela n'a pas toujours fonctionné et il a clignoté au moment du redémarrage. lumière allumée,même pendant quelques secondes, mais néanmoins. Voici à quoi ressemblait le contrôleur à ce stade:
Et puis il a été décidé d'abandonner complètement les boucliers Ethernet. J'ai commencé à chercher d'autres fonctionnalités de gestion à distance. J'ai essayé de connecter des arduins directement au serveur et d'envoyer des commandes via Serial.read () / Serial.print () - cela a fonctionné une fois sur deux, la stabilité n'a pas été atteinte car la carte a redémarré à chaque fois qu'elle était accessible à partir d'un script sur le serveur. J'ai beaucoup lu sur ces erreurs, je viens de réaliser qu'il était connecté au DTR, quelque part ils ont écrit que vous pouvez initialiser le port avec d'autres drapeaux, ont donné des exemples qui ne fonctionnaient pas pour moi. Après un certain temps, je suis tombé sur un article sur la fabrication d'un adaptateur USB-I2C à partir d'un programmeur USBAsp. J'ai décidé - pourquoi pas? J'ai commandé deux de ces programmeurs aux Chinois - et j'ai attendu.Et il y a une semaine, mon colis est arrivé. Un programmeur a été flashé avec le micrologiciel USB minuscule i2c et je me suis de nouveau assis pour réécrire le code. Ici, les fonctionnalités du protocole ont commencé à apparaître. Bien sûr, le serveur était le maître et toutes les cartes Arduino étaient des esclaves.Le code doit résoudre les tâches suivantes:- signaler l'état du port demandé;- changer l'état du port demandé;- éteignez ou allumez toutes les lumières.Et j'ai rencontré un problème. Je peux utiliser les commandes standard de la suite i2c-tools pour communiquer avec les cartes Arduino. C'est une équipei2cget -y < > <>
eti2cset -y < > <> 0x00 <byte >
Il semble que, à en juger par l'homme, vous pourriez passer le mot valeur, mais cela n'a pas fonctionné pour moi, ou je me suis trompé quelque part.Le problème est que, tout d'abord, si je dois changer l'état d'un certain port, je dois d'abord transmettre le numéro de la commande responsable de cette opération, puis le numéro de port. J'ai même essayé de le faire, d'envoyer 2 commandes et, dans le code, de les collecter à leur tour - mais c'était moche et irrationnel.Problème numéro deux - Arduino ne peut pas répondre à quelque chose au moment où elle reçoit les données. Il existe deux méthodes de la bibliothèque Wire: onReceive () lorsque les données sont reçues du maître et onRequest () lorsque les données sont demandées par le maître.J'ai donc fait ceci: le serveur web a 6 commandes:- commande "1" - obtenir l'état de toutes les lumières
- commande "2" - obtenir l'état de blocage de tous les commutateurs
- commande "5" - allumer le monde entier
- commande "6" - éteindre le monde entier
une commande formée comme suit: un nombre décimal de la forme suivante <centaines (1 ou 2) - allumer la lampe ou verrouiller l'interrupteur> <numéro de port (de 0 à 99)>; par exemple, 105 - port du commutateur 5, 213 - verrouillage du port du commutateur 13. Cette commande est traduite en hexadécimal et transmise à l'arduino, qui effectue les transformations inverses et comprend ce qui doit être fait.Voici à quoi cela ressemble du côté serveur: ...
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);
...
Et du côté de l'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);
}
}
}
Cela semble assez primitif, mais cela fonctionne. Le deuxième problème est résolu comme suit. Voici la fonction 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;
}
}
Avec les équipes 3-6, tout est clair, mais les équipes 1 ou 2 peuvent être décrites plus en détail. Le serveur envoie d'abord la commande souhaitée, et lorsque l'arduino reçoit la commande 1 ou 2, les drapeaux sont initialisés: start_request = true;
request_type = 1;
current_port = 0;
Et puis le serveur commence à envoyer des requêtes à l'arduino autant de fois que les ports qu'il souhaite interroger. Côté serveur: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 un mot - nous faisons plusieurs tentatives ($ tryes> 3) pour interroger l'arduino, nous obtenons une ligne composée de 0 ou 1. Partout avec n'importe quelle commande, nous regardons la réponse, s'il y a le mot Erreur - cela signifie qu'il y a eu des erreurs pendant le transfert et vous devez répéter le transfert . Plusieurs tentatives ont été faites pour garantir l'exactitude de la chaîne transmise, le tableau est réduit par chaîne en utilisant la méthode array_count_values ($ str); et à la fin, nous obtenons un tableau avec le nombre d'occurrences des mêmes lignes, nous donnons la ligne que nous avons reçue le plus de Arduino.Du côté d'arduino, tout est plus 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;
}
}
Le code de la page Web contient les contrôles:<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 fait, j'indique explicitement l'adresse de la carte à laquelle j'ai besoin d'accéder et le numéro de port.Oh oui, le contrôleur ressemble à ceci maintenant:
Une fois que tout a été installé, tout a commencé la première fois. Et le lendemain, un effet incompréhensible s'est produit: si vous allumez toutes les lumières de la chambre, toute la lumière de la chambre commence à clignoter toutes les 2 à 3 secondes. J'ai passé toute la nuit à choisir le code; il n'y avait pas un tel montant sur le banc d'essai, donc le problème n'est pas dans le code. J'ai fouillé dans un tas de forums, dans une toge sur l'un d'eux, j'ai trouvé une description d'un symptôme similaire, vérifié ma supposition - et le problème a disparu. Et le fait est que j'ai alimenté les trois arduins à partir d'une alimentation électrique d'ordinateur, 12V, et que le vieil affranchi a tranquillement mangé et n'a pas bourdonné, et arduino uno v3 ne pouvait pas, et quand tous les relais ont été allumés, le régulateur de puissance a été chauffé de telle sorte que C'était impossible à toucher. Réduit la tension d'alimentation à 5V 2A - et cela a fonctionné comme il se doit.Il y a beaucoup de plans, nous devons terminer la réparation dans l'appartement, le couloir et la salle de bain avec les toilettes sont en ligne, dans mes rêves, je veux contrôler le chauffe-eau et chaque sortie, car maintenant je peux accrocher n'importe quelle quantité d'arduin sur le bus I2C et chacun fera sa propre chose. Il est également prévu d'ajouter des relais d'impulsions au rail DIN afin que les modules de relais ne contrôlent que ces relais d'impulsions, et déjà la charge entière passe par ces derniers. Parce qu'il existe de sérieux doutes sur la fiabilité des modules relais chinois. Mais c'est tout à l'avenir.Comme promis, liens vers github:interface Web Sketchypour arduino