СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Беспроводной датчик температуры/влажности на Attiny13. Интеграция в систему умного дома в черновиках



Иногда в системах умного дома применяя беспроводные технологии не имеет смысла использовать сложные и дорогие модули, например те же 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 кОм:

image

Если вы захотите мониторить ещё и влажность, то 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, который обрабатывает и заносит данные с датчиков в базу данных.

Для начала создадим таблицу с датчиками и таблицей логов в mysql
CREATE 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)