Иногда в системах умного дома применяя беспроводные технологии не имеет смысла использовать сложные и дорогие модули, например те же NRF24LE1 или NRF24L01+М/К, если необходима только односторонняя связь, например для передачи показаний датчиков влажности или/и температуры- в этом случае можно использовать очень простые решения на базе дешевого микроконтроллера attiny13 и передатчика на 433 или 315 мгц. Данный способ я упоминал на хабре и он описан в моем
блоге. На фото выше можно разглядеть датчик ds18b20 с резистором, attiny13 на платке-переходнике, радиопередатчик с антенной, а так же кейс для батареек 3хААА.
Принцип действия
Алгоритм передачи данных позаимствован от микросхем шифроватор/дешифратор SC2272/SC2262, которые применяются в разнообразных беспроводных розетках и люстрах. Для чтения данных радиопультов существуют две распространенные библиотеки Arduino — RemoteSwitch (троичный протокол) и RCswitch (двоичный протокол). Эти библиотеки могут принимать команды от пультов и от данных беспроводных датчиков одновременно. Результат выдается в виде целого числа 0 до 531440. (3 в 12 степени) у RemoteSwitch, или 0 до1048576. ( 2 в 20 степени) у RCswitch.
Рассмотрим принцип кодирования температуры:
Например передаем температуру из переменной float с одним знаком после запятой:
Имеем переменную temp и для её передачи мы должны провести определенные математические операции:
1.Умножаем на 10, чтобы избавится от знаков после запятой.
2.Прибавляем 500 для того чтобы без проблем отправить отрицательную температуру.
Получаем:
temp*10+500+код-идентификатор =число, которое можем передать по радиоэфиру.
Декодируем температуру:
Принимаем число и проводим данные логические операции в обратном порядке:
if (key<=receivedCode && key+999>receivedCode) temp=(receivedCode-key1-500)/10;
Где receivedCode — это принятый код, а key — это код-идентификатор, который был задан на передаче.
Код-идентификатор должен быть у всех данных разный и не должен пересекаться с кодами радиопультов, если таковые используются…
temp — полученная температура с одним знаком после запятой.
При отправке данных влажности, т.к. там нет отрицательных значений, то добавление и вычитание числа 500 не требуется.
Схема устройства
Если беспроводной датчик будет передавать только температуру, то схема состоит всего из 4 элементов: микроконтроллера, радиомодуля, датчика ds18b20, резистор на 4.7 кОм:
Если вы захотите мониторить ещё и влажность, то ds18b20 заменяем на DHT22. Резистор тут даже можно опустить…
Микроконтроллер Attiny13 может быть заменен на Attiny25, Attiny45, Attiny85 и аналогичные.
Другие незадействованные ноги микроконтроллера могут быть использованы для подключения дополнительных датчиков, в том числе и аналоговых через АЦП.
Прошивка беспроводного датчика
Ниже рассмотрим пример упрощенного беспроводного датчика температуры без спящего режима:
Код датчика на С#/* Name: main.c
ds18b20
*/
#define timer 1 // время в секундах (умноженное на 5) опрос и отправка данных с датчика.
#define periodusec 400 // mcs
#define DS_BIT 4 // pin 3
#define RC_BIT 3 // pin 2
#define keyT 11000
#include <avr/io.h>
#include <util/delay.h> /* for _delay_us() */
void sendRC(unsigned long data) { // Отправка данных по радиоканалу RCswitch. Двоичный протокол
DDRB |= _BV(RC_BIT);
data |= 3L << 20; // ?
unsigned short repeats = 1 << (((unsigned long)data >> 20) & 7);
data = data & 0xfffff;
unsigned long dataBase4 = 0; uint8_t i;
for (i=0; i<20; i++) {
dataBase4<<=1;
dataBase4|=(data%2);
data/=2;
}
unsigned short int j;
for (j=0;j<repeats;j++) {
data=dataBase4; uint8_t i;
for (i=0; i<20; i++) {
switch (data & 1) {
case 0:
PORTB |= _BV(RC_BIT);
_delay_us(periodusec);
PORTB &= ~_BV(RC_BIT);
_delay_us(periodusec*3);
break;
case 1:
PORTB |= _BV(RC_BIT);
_delay_us(periodusec*3);
PORTB &= ~_BV(RC_BIT);
_delay_us(periodusec);
break;
}
data>>=1;
}
PORTB |= _BV(RC_BIT);
_delay_us(periodusec);
PORTB &= ~_BV(RC_BIT);
_delay_us(periodusec*31);
}
}
// OneWire функции:
void OneWireReset()
{
PORTB &= ~_BV(DS_BIT);
DDRB |= _BV(DS_BIT);
_delay_us(500);
DDRB &= ~_BV(DS_BIT);
_delay_us(500);
}
void OneWireOutByte(uint8_t d)
{
uint8_t n;
for(n=8; n!=0; n--)
{
if ((d & 0x01) == 1)
{
PORTB &= ~_BV(DS_BIT);
DDRB |= _BV(DS_BIT);
_delay_us(5);
DDRB &= ~_BV(DS_BIT);
_delay_us(60);
}
else
{
PORTB &= ~_BV(DS_BIT);
DDRB |= _BV(DS_BIT);
_delay_us(60);
DDRB &= ~_BV(DS_BIT);
}
d=d>>1;
}
}
uint8_t OneWireInByte()
{
uint8_t d, n,b;
for (n=0; n<8; n++)
{
PORTB &= ~_BV(DS_BIT);
DDRB |= _BV(DS_BIT);
_delay_us(5);
DDRB &= ~_BV(DS_BIT);
_delay_us(5);
b = ((PINB & _BV(DS_BIT)) != 0);
_delay_us(50);
d = (d >> 1) | (b<<7);
}
return(d);
}
//------------------------------------------------
//-----------------Main программа-----------------
//------------------------------------------------
int __attribute__((noreturn)) main(void)
{
uint8_t delay_counter=0;
// установка режима для ds
DDRB |= _BV(DS_BIT);
PORTB &= ~_BV(DS_BIT);
DDRB &= ~_BV(DS_BIT);
for(;;){ /* main event loop */
_delay_ms(5000);
if (delay_counter==0) {
delay_counter=timer;
// ds
uint8_t SignBit;
uint8_t DSdata[2];
OneWireReset();
OneWireOutByte(0xcc);
OneWireOutByte(0x44);
PORTB |= _BV(DS_BIT);
DDRB |= _BV(DS_BIT);
//delay(1000); // если хотим ждать когда датчик посчитает температуру.
DDRB &= ~_BV(DS_BIT);
PORTB &= ~_BV(DS_BIT);
OneWireReset();
OneWireOutByte(0xcc);
OneWireOutByte(0xbe);
DSdata[0] = OneWireInByte();
DSdata[1] = OneWireInByte();
int TReading = (int)(DSdata[1] << 8) + DSdata[0];
SignBit = TReading & 0x8000;
if (SignBit) TReading = (TReading ^ 0xffff) + 1;
sendRC(((6 * TReading) + TReading / 4)/10+500+keyT); // отправляем данные
}
delay_counter--;
}
}
Код датчика достаточно простой и содержит только основные функции, сюда же можно добавить спящий режим для экономии энергии батареи и чтение аналогового входа.
Для тех, кто не знаком с С# и не хочет замарачиваться с установкой компиляторов, был написан
конструктор для создания прошивки. Тут необходимо только установить необходимые параметры и скачать готовый файл прошивки.
Прошить микроконтроллер можно как через Arduino так и через программаторы, например через популярный и дешевый USBasp.
Ethernet сервер на базе Arduino
Для приема данных необходим какой-то сервер-приемник на базе микроконтроллера, который будет отправлять данные уже на сервер «умного дома».
Код сервера на arduino с Ethernet шилдом w5100#include <RCSwitch.h>
#include <Ethernet.h>
#include <SPI.h>
#include <Arduino.h>
#include "WebServer.h"
RCSwitch mySwitch = RCSwitch();
unsigned long valueold;
char buf[60]; //буфер для отправки данных.
byte ip[] = { 192, 168, 1, 10 }; // локальный адрес
byte rserver[] = { 192, 168, 1, 20 }; // ip-адрес удалённого сервера
byte mac[] = { 0xDE, 0xAD, 0xBE, 0x54, 0xDE, 0x33 }; // MAC-адрес нашего устройства
byte subnet[] = { 255, 255, 255, 0 };
byte gateway[] = { 192, 168, 1,1 };
byte dns_server[] = { 192, 168, 1, 1};
#define VERSION_STRING "Radio server"
P(Page_info) = "<html><head><title>controller " VERSION_STRING "</title></head><body>\n";
P(location_info) = "server room";
P(version_info) = VERSION_STRING ". Compile date: " __DATE__ " " __TIME__;
String url = String(25);
#define MAX_COMMAND_LEN (10)
#define MAX_PARAMETER_LEN (10)
#define COMMAND_TABLE_SIZE (8)
#define PREFIX ""
WebServer webserver(PREFIX, 80);
EthernetClient client;
#define NAMELEN 32
#define VALUELEN 32
char gCommandBuffer[MAX_COMMAND_LEN + 1];
char gParamBuffer[MAX_PARAMETER_LEN + 1];
long gParamValue;
typedef struct {
char const *name;
void (*function)(WebServer &server);
} command_t;
void infoRequest(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
server.printP(Page_info);
server.print("IP:");
server.print(Ethernet.localIP());
server.print("<br>Location:");
server.printP(location_info);
}
void setup() {
Ethernet.begin(mac, ip, dns_server, gateway, subnet); // Инициализируем Ethernet Shield
webserver.setDefaultCommand(&infoRequest); // дефолтная страница
webserver.begin();
mySwitch.enableReceive(0); // включаем прием на 2 выводе (прерывание 0)
}
bool ledA=0,ledB=0,ledC=0,ledD=0;
long statesend = 0;
void loop() {
char buff[64];
int len = 64;
webserver.processConnection(buff, &len); // process incoming connections one at a time forever
unsigned long currentMillis = millis(); // текущий счетчик
if (mySwitch.available()) {
unsigned long valuesend = mySwitch.getReceivedValue();
#if 0 // прием команд с радиопультов, выключено ! При использовании не забудьте про установку пина режим на выход..
if ((unsigned int)valuesend==21952){ // A
if (currentMillis-statesend>1000){
statesend=currentMillis;
ledA=!ledA;
sendstate(5,ledA);
}
} else
if ((unsigned int)valuesend==21808){ // B
if (currentMillis-statesend>1000){
statesend=currentMillis;
ledB=!ledB;
sendstate(6,ledB);
}
} else
if ((unsigned int)valuesend==21772){ // C
if (currentMillis-statesend>1000){
statesend=currentMillis;
ledC=!ledC;
sendstate(7,ledC);
}
} else
if ((unsigned int)valuesend==21763){ // D
if (currentMillis-statesend>1000){
statesend=currentMillis;
ledD=!ledD;
sendstate(8,ledD);
}
} else
#endif
// отправка принятых данных с беспроводных датчиков и отправка их на сервер.
if (valuesend != 0 && valueold !=valuesend)
{
valueold=valuesend;
if (client.connect(rserver, 80)) {
sprintf (buf,"GET /server.php?mode=rcskey&n=%hu HTTP/1.0",valuesend);
client.println(buf);
client.println("Host: local.ru");
client.println();
} //else Serial.println("connection failed");
client.stop();
}
mySwitch.resetAvailable();
}
}
void sendstate (int pin,int mode){ // управление нагрузками с пульта с отправкой статуса на сервер
digitalWrite(pin,mode);
if (client.connect(rserver, 80)) {
sprintf(buf, "GET /server.php?mode=key&n=%i&s=%i HTTP/1.0", pin, mode);
client.println(buf);
client.println("Host: local.ru");
client.println();
}
client.stop();
}
Данный код принимает сигналы с беспроводных датчиков, а так же с радиопультов.
Приемник должен быть подключен к 2 выводу ардуины.
Можно аналогично реализовать и без ethernet, используя последовательный порт.
Обработка принятых данных на сервере
При приеме данных с беспроводных датчиков они пересылаются на компьютер, на котором должен быть запущен веб сервер с поддержкой PHP. Arduina обращается к скрипту server.php, который обрабатывает и заносит данные с датчиков в базу данных.
Для начала создадим таблицу с датчиками и таблицей логов в mysqlCREATE TABLE IF NOT EXISTS `logsensors` (
`sensor` varchar(16) NOT NULL,
`val` varchar(12) NOT NULL,
`dtm` varchar(20) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1029966 ;
CREATE TABLE IF NOT EXISTS `sensors` (
`idd` varchar(16) NOT NULL, -- ид датчика
`name` varchar(35) NOT NULL, -- имя датчика
`type` varchar(3) NOT NULL, -- тип данных:
`val` varchar(12) NOT NULL, --текущие данные датчика
`dtm` varchar(20) NOT NULL, --дата и время
`enable` int(1) NOT NULL, -- датчик разрешен
`logenable` int(1) NOT NULL, -- разрешить логи сенсоров
`hidden` varchar(3) NOT NULL,
`period` int(2) NOT NULL DEFAULT '5', -- период записи в базу в минутах
`radiost` int(1) NOT NULL -- статус датчика:1-не работает(таймаут),0 - работает
) ENGINE=MyISAM DEFAULT CHARSET=cp1251;
INSERT INTO `sensors` (`idd`, `name`, `type`, `val`, `dtm`, `enable`, `logenable`, `hidden`, `period`, `key`, `radiost`) VALUES
( '11000', 'С/У радио', 'T', '22.3', '2014-09-09 13:20:15', 1, 1, 0, 5, 0, 1),
( '12000', 'Кухня', 'T', '20', '2014-09-09 13:21:16', 1, 1, 0, 5, 0, 1);
Файл server.php<?php
// не забудьте задать параметры подключения к БД.
$link = mysql_connect($dbhost, $dbuser, $dbpassword);
mysql_select_db($dbname, $link);
$dtm=date('Y-m-d H:i:s');
$mode = $_GET['mode'];
if ($mode=="key") { // команда от радиопульта
$state=$_GET['s'];
$pin=$_GET['n'];
mysql_query ("UPDATE `kkeys` SET `state` = '$state' WHERE `idd` = '$pin'");
}else if ($mode=="rcskey") { // команды от беспроводных датчиков
$receivedCode=$_GET['n'];
$m = mysql_query ("SELECT * FROM sensors WHERE mode LIKE '1'");
for ($c=0;$c<mysql_num_rows($m); $c++) {
$fe = mysql_fetch_array($m);
if ($fe[idd]<=$receivedCode && $fe[idd]+1024>$receivedCode) {
if ($fe[type]=="H") $tdata=($receivedCode-$fe[idd])/10; // прием влажности
else if ($fe[type]=="T") $tdata=($receivedCode-$fe[idd]-500)/10; // прием температуры
else $tdata=$receivedCode-$fe[idd]; // прием просто число, например от аналогового датчика
mysql_query ("UPDATE `sensors` SET `val` = '$tdata',`dtm` = '$dtm',`radiost` = '1' WHERE `idd` = '$fe[idd]'");
}
}
}
Для того чтобы записывать статистику температуры необходимо скрипт ниже вписать в планировщик на каждые 5 минут:
Код для планировщика<?php
// не забудьте задать параметры подключения к БД.
$link = mysql_connect($dbhost, $dbuser, $dbpassword);
mysql_select_db($dbname, $link);
$m = mysql_query ("SELECT * FROM sensors WHERE mode LIKE '1' AND logenable LIKE '1' AND radiost LIKE '1'");
for ($c=0;$c<mysql_num_rows($m); $c++) {
$p = mysql_fetch_array($m);
$res=date('i')/$p[period];
// заносим в БД ,только если датчик обновлялся за последний период времени.
if (round($res)==$res) mysql_query("insert into logsensors (sensor,val,dtm) values('$p[idd]','$p[val]','$p[dtm]')");
mysql_query ("UPDATE `sensors` SET `radiost` = '0' WHERE `idd` = '$p[idd]'");
}
Периодичность записи в базу статистики зависит от времени, установленное в столбце period в таблице sensors.
Исходные коды выше предоставлены в качестве примеров, на самом же деле дома работает система с бОльшими функциями, в которой так же имеются и проводные датчики и прочее… Веб интерфейс управления «умным домом» у меня свой, но показывать там пока не чего -его даже сложно назвать «умным домом». Данные примеры можно интегрировать в систему
MajorDoMo или же написать свою.
В данный момент у меня в работе 2 беспроводных датчика температуры, которые передают так же данные с входа АЦП( в будущем АЦП будет использоваться для датчика протечки). Датчики, а так же пульт работают на частоте 315мгц.
Преимущества и недостатки
Преимущества данного решения:
- Дешево. Примерно 3-4 $ за всю схему на базе DS18B20 (по ценам ebay).
- Схема очень простая.
- Достаточно продолжительная работа от батарей.
- Можно использовать этот же канал для управления через радиопульт.
Недостатки:
- Нет шифрования, а зачем ?..
- Дальность не такая уж большая, но достаточна, если у вас не дворец..
комментарии (0)