Monday, May 6, 2013

More RF433 Wireless: Home Automation

The RF433 transmitter and receiver modules can be used with Home Automation equipment. This blog post presents the Cosa support for sending and receiving wireless commands for NEXA/HomeEasy equipment. With sensor modules and supporting drivers it is possible to build advanced Home Automation applications that both receive and send commands to monitor and control lighting, alarms, door-bells, heaters, fans, etc. The Cosa NEXA/HomeEasy may be used with ATtiny.

Fig.1: CosaNEXAreceiver on ATtiny85 with RF433 Receiver and NEXA Remote

The Cosa NEXA::Receiver allows Arduino sketches to listen for wireless commands. These may be transmitted by a standard remote as in the figure(1) above or from another Arduino running the NEXA::Transmitter as in the figure(2) below. The receiver supports message decoding and address matching hiding much of the protocol complexity. Below is an example sketch (CosaNEXAreceiver.ino). The command data type (NEXA::code_t) simplifies the access of the command members. It also supports address matching.  

OutputPin led(Board::LED);
NEXA::Receiver receiver(Board::EXT0);
NEXA::code_t device = 0;
...
void setup()
{
  ...
  // Use polling version to receive the remote button for device
  receiver.recv(device);
  trace << PSTR("learning: ") << device << endl;
  ...
  // Enable the interrupt driven version
  receiver.enable();
}
...
void loop()
{
  // Wait for the next event
  Event event;
  Event::queue.await(&event);
  ...
  // Get the received command code and check if it is for this device
  NEXA::code_t code = receiver.get_code();
  if (code == device) {
    trace << PSTR("matched: ") << code << endl;
    led << code.onoff;
  }
}


The NEXA::Receiver uses an external interrupt pin, Board::EXT0. Both a polling (busy-wait) and interrupt driven version are provided. In the sketch above the polled version, receiver.revc(), is used to receive the first command code. This code is used as the device address for matching command codes received from the interrupt driven version in the loop() function.

Fig.2: Arduino Nano with RF433 Transmitter and NEXA Receiver

An event is pushed by the interrupt handler when a command code has been received and decoded. The loop() simply waits for the next event, retrieves the received command code, get_code(), and matches it with the operator==. If the unit address matches the received code is printed to the trace stream and the LED is turned on according to the onoff member of the received command code. The Cosa NEXA class also contains a command code transmitter, NEXA::Transmitter. The current version of send() is delay-based. The below example sketch (CosaNEXAsender.ino) is the wireless version of the Blink sketch.

OutputPin led(Board::LED);
NEXA::Transmitter transmitter(Board::D9, 0xc05a01L);
...
void setup()
{
  ...
  // First code will be used by receiver as address (learning mode)
  transmitter.send(0, 1);
}
...
void loop()
{
  // Blink the transmitter and receiver leds
  led.on();
  transmitter.send(0, 1);
  SLEEP(1);
  transmitter.send(0, 0);
  led.off();
  SLEEP(5);
}


The NEXA::Transmitter constructor requires an output pin and the house address (26-bit). The unit number is given as a parameter to the send() method. There is also a broadcast() method to send a group-message.

  union code_t {
    int32_t as_long;
    struct {
      uint8_t device:4;       /** device number, group:unit<2,2> */
      uint8_t onoff:1;        /** device mode, off(0), on(1) */
      uint8_t group:1;        /** group command */
      uint32_t house:26;      /** house code number */
    };
    code_t(int32_t value = 0L);
    code_t(int32_t h, uint8_t g, uint8_t d, uint8_t f);
    bool operator==(const NEXA::code_t &other) const;
    friend IOStream& operator<<(IOStream& outs, code_t code);
  };


The last example sketch in this post integrates the Virtual Wire Interface (VWI) and NEXA Transmitter to form a wireless Thermostat. The sketch receives humidity and temperature readings from the wireless DHT11 sketch (CosaTinyDHT11.ino) running on a ATtiny85. When temperature drops too low a relay/heater is turned on and when the humidity is too high a relay/fan is turned on and a command is send to a NEXA receiver (vent). When the readings drop below a lower threshold the heater/fan/vent are turned off again.

Fig.3: CosaTinyDHT11 running on an ATtiny85 and receiver

Two support classes, Relay/RemoteRelay, are defined to make the setup() and loop() easier to read. Below is the setup() of CosaVWIthermostat.ino. The setup() starts the Virtual Wire Interface (VWI) and receiver, and sends a NEXA command to the vent.

Relay heater(Board::D2);
Relay fan(Board::D3);
RemoteRelay vent(Board::D4, 0xc05a01L, 0);
...
void setup()
{
  ...
  // Start virtual wire interface and receiver
  VWI::begin(SPEED);
  rx.begin();
  ...
  // Close the remote vent
  vent.close();
}

...
// Message type to receive
struct msg_t {
  uint16_t nr;
  int16_t humidity;
  int16_t temperature;
};
...
void loop()
{
  // Wait for a message
  msg_t msg;
  int8_t len = rx.recv(&msg, sizeof(msg));
  if (len != sizeof(msg)) return;
  int16_t humidity = msg.humidity;
  int16_t temperature = msg.temperature;
  ...
  // Check if heater should be turned on @ 22 C and off @ 26 C
  static const uint8_t TEMP_MIN = 22;
  static const uint8_t TEMP_MAX = 26;
  if (heater.is_on()) {
    if (temperature > TEMP_MAX) heater.off();
  }
  else if (temperature < TEMP_MIN) heater.on();
  ...
  // Check if fan/vent should be turned on @ 70 %RH and off @ 50 %RH.
  static const uint8_t RH_MIN = 50;
  static const uint8_t RH_MAX = 70;
  if (fan.is_on()) {
    if (humidity < RH_MIN) {
      fan.off();
      vent.close();
    }
  }
  else if (humidity > RH_MAX) {
    vent.open();
    fan.on();
  }
}


Please see the Cosa examples folder for the full listing of the example sketches.

[Update 2013-06-02]

Since the first publishing of this post the NEXA device driver has been improved to make it even easier to handle Remote Control device actions. A NEXA::Receiver::Device class has been added. Below is an example of usage:

class LED : public NEXA::Receiver::Device {
private:
  OutputPin m_pin;
public:
  LED(Board::DigitalPin pin) : NEXA::Receiver::Device(0L), m_pin(pin) {}
  virtual void on_event(uint8_t type, uint16_t value) { m_pin << value; }
};
...
NEXA::Receiver receiver(Board::EXT0);
LED device(Board::LED);
...
void setup()
{
  ...
  // Use polling version to receive the remote button to attach
  NEXA::code_t cmd;
  receiver.recv(cmd);
  device.set_key(cmd);
  receiver.attach(&device);
  ...
  // Enable the interrupt driven version of the receiver
  receiver.enable();
}


The NEXA::Receiver::Device class is used to create a LED output pin that is controlled by the NEXA Remote. The incoming command is filtered by the receiver and the matching device will receive event and the value (on/off/dim level). In the example above the event value is written to the output pin (m_pin). To bind the device to the receiver we need to 1) set the key (address) of the device, 2) attach the device to the receiver. That is all. Once the interrupt handler for the receiver is enabled all incoming commands are compared to the attached devices and events passed on a match. The loop() function looks like this:

void loop()
{
  // Wait for the next event and dispatch
  Event event;
  Event::queue.await(&event);
  event.dispatch();
}


This is the standard event loop for Cosa. Typically in an application there would be several devices and a special "learning" mode where the devices would be bound to buttons on the Remote. The keys would be stored in EEPROM and read on startup. The LED (NEXA::Receiver::Device) class can actually be used for any control that would be the toggling of an output pin and not just LEDs.

The NEXA classes may be used with both ATtinyX4 and ATtinyX5.

[Update 2015-08-22]
The new support library Cosa Domotica makes it really easy to develop RF433 based sensor nodes.  The library performs common operations such as battery status measurement, message sequencing, low power sleep modes, etc.