很长时间以来,我在Habré上阅读了有关家庭自动化系统的文章,我想描述我已经从事了2年多的工作。为了更好地了解我的情况,您需要做一个简短的介绍。三年前,我和我的家人搬到了一套新的三居室公寓(67.5平方米),尽管从技术上讲公寓当然是旧的-斯大林,这是一栋建于1946年的房子。铝制两线制电线,某些地方用1平方毫米的铜绞线组成。检修工作即将进行,我决定自己动手做,并从彻底更换布线开始。他们购买了700m的电力电缆,用于照明和1.5和2.5平方毫米的插座,双绞线托架,一些用于电视天线的同轴电缆(以防万一)。为什么这么多以及产生了什么-我要一只猫。我决定立即进行布线,即:没有配电箱,电缆从每个点到屏蔽层(除了插座(可以是2-3点的一组),电缆从极端到屏蔽层,其余通过回路连接)-t。 e。以防万一,从每个开关将其自己的电缆连接到屏蔽层,从每个照明点将其自己的电缆连接到屏蔽层,沿rj-45点对的插座附近。当然,电缆很多。遵循PUE的所有规则将其放置在地板
上的某个地方,例如在托儿所中:某个地方-在天花板上,例如卧室门的视图:
结果,我们将所有电缆都放置在将放置屏蔽的地方的走廊中,并且可以根据需要在它们之间进行切换。即使将来必须更改连接方案,也不会花费太多时间,也不必破坏维修。当然,在所有场合都制作了许多插座-整个公寓总共约90点。这是实验过程中临时的“防护罩”的样子:
可怕的景象,但一切正常,这种怪物以这种形式生活了几个月。晴朗的一天,星星成功地形成了,而盾牌又经过了故意的决定而重做。在天花板下的摇摇欲坠的楼梯上花了3天(斯大林的天花板在3m内),但这是值得的。此后,遮阳板开始看起来像这样:
和一般视图:
这与屏蔽的最终形式相去甚远,仍然没有足够的RCD,断路器的数量比必要的少3倍,没有脉冲继电器-但是有非断开线,所有电缆都连接到端子,至少具有某种断路器选择性。既然您对我必须使用的东西有一个大致的了解,那么您就可以开始描述系统的``大脑''了。事不宜迟,我以arduino为基础,尤其是因为我早就购买了2个freeduino板,2个以太网防护罩和一个电动机防护罩。还从中文订购了几个继电器模块,每个模块4个。我还发现,根据Ob上Habré上一篇文章的建议,可以在其中插入特殊弹簧的开关会变成不固定的开关。是时候编写代码了。总的来说,我与生活中的计算机息息相关。他完成了自己没写过的皮草垫-pascal,c#,c ++,1c,php,javascript-您仍然可以列出很多。我上一次在Web开发领域工作时,我还处理ip-telephony。因此,提出一种算法很简单,但是对于“为您服务”的电子产品,我知道并知道简单的事情,而涉及到更复杂的事物(微控制器,保险丝,触点弹跳)则更加困难。但是不是神在烧锅,眼睛在害怕,但是手在烧。我决定简化一切。我将arduino的大地馈送到开关的输入,将开关的输出连接到arduino的模拟引脚(尽管可以数字化,但这并不重要)。我将arduino的引脚与继电器模块上的引脚相连。从技术上讲,一切准备就绪,当您按下开关时,arduino会在所需引脚上看到LOW值,并在与继电器模块相连的引脚上设置LOW。假设所有从公寓中的点到终端的电缆都已连接到终端,则连接过程不会花费很多时间。为了应对联系反弹,在互联网上研究了主题之后,选择了Bounce2库。通常,我最初想编写一个通用代码,以便它可以与至少2个开关(至少22个)一起使用。正是这一任务构成了整个算法的基础。好吧,现在从单词到代码。我不会完全上传所有代码;到github的链接将在本文的结尾。从我的角度来看,我只会展示重要的时刻。因此,声明库和变量:
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];
重点是:随灯/开关的数量增加常数并在阵列中注册新的关联就足够了-就是说,将添加新的灯或开关。同时,代码结构允许一个开关一次控制多个灯具。或几个开关立即控制一盏灯。此外,对于每个开关,都会创建其自己的Bounce对象实例以进行反跳动。在设置功能中,所有阵列均被初始化,而照明设备的状态存储在非易失性存储器中,以便在发生电源故障或重新启动时,一切都与发生故障之前相同。 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);
}
关于第一个周期,我什么也不会说,我们稍后再讲。最有趣的开始是主体循环的主体。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 );
}
在测试算法的第一个版本时,注意到孩子(我有三个,男孩)真的很喜欢单击开关。因此,有必要能够关闭某些开关,以使控制器不响应它们。最明显的选择是简单地从板上移去必要的引脚,但这是错误的,没有意思。因此,还将标志写入非易失性存储器,以指示开关是否断开。这是使用以下循环初始化的: for(byte i=0; i<cnt; i=i+1) {
EEPROM.write(pins[i], 10);
}
...
并在这里检查: for(byte i=0
byte dvalue = EEPROM.read(pins[i])
if(dvalue!=11) {
...
在这个阶段,墙壁上的本地开关已经开始起作用。但是,如果没有界面,智慧家庭将不会变得聪明。由于我正在从事Web开发,因此决定创建一个Web界面。这就是以太网屏蔽派上用场的地方。不幸的是,我找不到使用以太网屏蔽进行远程控制的程序的第一个版本的源。我将尝试搜索备份,也许它们在那里。但是含义是原始的,可耻的。每个控制器都有自己的IP地址。 Web服务器在arduino上上升,该服务器分析GET请求并根据端口号打开或关闭相应的指示灯。 Internet上有很多此类示例。对于Web界面,服务器是在主板上内置英特尔Atom的服务器,已安装Ubuntu Server 14.02,已安装标准的LAMP套件,一个简单的界面被写在那里。所有资源也将在本文的结尾。目前,它看起来像这样:
如您所见,厨房中的一盏灯已打开,并且负责该开关的开关已锁定。管理非常简单-只需单击所需的项目,即可更改其状态。如果不是一个“但是”,一切都会很棒。以太网防护罩一直挂着。不,来自交换机的本地控制始终像时钟一样工作。但是从Web界面进行的远程控制不断下降。如果管理层工作一天,那就太好了。但是更常见的是,重新启动后仅几个小时,防护罩就挂了。我只是不尝试处理看门狗,但我的董事会不支持它。我在中国订购并在en28j60上用其他盾牌替换了盾牌,它变的更好了,但仍然定期挂断。我向控制器添加了一个继电器模块,通过其常闭触点为arduino板供电,其中一个arduino板以一定的频率拉动了继电器,并切断了电源,然后恢复了该状态-但是,这也并不总是起作用,并且在重启时闪烁点亮,即使是几秒钟,但仍然如此。这是控制器此时的外观:
然后决定完全放弃以太网屏蔽。我开始寻找其他远程管理功能。我试图将arduins直接连接到服务器,并通过Serial.read()/ Serial.print()发送命令-它每隔两次就工作一次,但由于每次从脚本访问它时板都重新启动,因此无法实现稳定性在服务器上。我读到了很多有关此类错误的信息,我只是意识到它与DTR相连,在他们写道可以用其他标志初始化端口的地方,给出了对我不起作用的示例。一段时间后,我碰到一篇有关用USBAsp编程器制作USB-I2C适配器的文章。我决定-为什么不呢?我从中文订购了几个这样的程序员-然后等待。一周前,我的包裹到达了。一个程序员用i2c微型USB固件刷新,然后我再次坐下来重写代码。此处的协议功能开始出现。当然,服务器是主机,所有Arduino板都是从机。该代码应解决以下任务:-报告所请求端口的状态;-切换请求端口的状态;-关闭或打开所有灯。我遇到了一个问题。我可以使用i2c-tools套件中的标准命令与arduino板进行通信。这是一个团队i2cget -y < > <>
和i2cset -y < > <> 0x00 <byte >
从芒的角度来看,您似乎可以传递“值”一词,但这对我没有用,或者我在某个地方弄错了。问题是,首先,如果需要切换某个端口的状态,则必须首先传递负责此操作的命令的编号,然后再传递端口号。我什至尝试执行此操作,发送2个命令,并在代码中依次收集它们-但是这很丑陋且不合理。第二个问题-Arduino在收到数据时无法回答某些问题。Wire库有两种方法-从主机接收数据时使用onReceive(),当主机请求数据时使用onRequest()。所以我这样做:Web服务器有6个命令:- 命令“ 1”-获取所有指示灯的状态
- 命令“ 2”-获取所有开关的阻塞状态
- 命令“ 5”-打开整个世界
- 命令“ 6”-关闭整个世界
命令的格式如下:十进制数,其格式为<百(1或2)-切换灯或开关锁> <端口号(从0到99)>;例如105-开关5端口,213-开关13端口锁定。该命令被转换为十六进制并传递给arduino,后者执行逆变换并了解需要执行的操作。这是服务器端的样子: ...
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);
...
从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);
}
}
}
它看起来很原始,但是可以工作。解决第二个问题如下。这是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;
}
}
对于第3-6队,一切都很明确,但是第1或第2队可以更详细地描述。服务器首先发送所需的命令,并且当arduino收到命令1或2时,将初始化标志: start_request = true;
request_type = 1;
current_port = 0;
然后,服务器开始向arduino发送请求的次数与其要轮询的端口的次数相同。在服务器端: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);
简而言之-我们进行了几次($ tryes> 3)次尝试来询问arduino,我们得到了一条由0或1组成的行。我们可以在任何地方用任何命令查看答案,如果有单词Error的话-这意味着传输过程中存在错误,您需要重复传输。为了确保传输的字符串的正确性,我们进行了多次尝试,使用array_count_values($ str)方法按字符串折叠数组; 最后,我们得到一个包含相同行出现次数的数组,我们给出了从arduino获得的大部分行。从arduino的角度来看,一切都更加简单: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;
}
}
该网页的代码包含以下控件:<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>
实际上,我明确指出了我需要访问的板卡地址和端口号。哦,是的,控制器现在看起来像这样:
一切都安装到位后,一切都在第一次启动。第二天,出现了难以理解的效果-如果打开卧室中的所有灯,卧室中的所有灯每2-3秒开始闪烁一次。我花了一整夜的时间挑选代码;测试台上没有这样的门框,所以问题不在代码中。我在多个论坛中翻阅,在其中一个论坛的toga中,我找到了类似症状的描述,检查了我的猜测-问题消失了。关键是我用计算机电源(12V)给所有三个arduins供电,而旧的freeduino安静地进食并且没有嗡嗡声,而arduino uno v3却不能,并且在打开所有继电器时,功率调节器被加热了,以便这是不可能的。将电源电压降低到5V 2A-并可以正常工作。有很多计划,我们需要完成公寓,走廊和带卫生间的浴室的维修,在我的梦里我想控制热水器和每个插座,因为现在我可以在I2C总线上挂任何数量的arduin,并且每个人都可以做自己的事。还计划在DIN导轨上增加脉冲继电器,以便继电器模块仅控制这些脉冲继电器,并且整个负载已经通过了后者。因为对中国继电器模块的可靠性存在严重怀疑。但这就是将来的一切。如承诺的那样,链接到github:arduino的 SketchyWeb界面