Hemautomation V2

Berätta om dina pågående projekt.
brrmek
Inlägg: 52
Blev medlem: 26 maj 2012, 23:27:37
Ort: Ursviken

Re: Hemautomation V2

Inlägg av brrmek »

Snyggt! Det finns väl en del lib skrivna till manchester code. Borde fungera på den signalen.
Användarvisningsbild
squiz3r
Inlägg: 5424
Blev medlem: 5 september 2006, 20:06:22
Ort: Lund
Kontakt:

Re: Hemautomation V2

Inlägg av squiz3r »

Uppdaterade matlab koden på förra sidan. Nu kollar den om den avkodade datan är konsistent (dvs samma data i alla sändningar den ser). Om den är det kombinerar den ihop delsträngarna till en komplett data sträng och parsar informationen i den. Outputen kan se ut såhär:

Kod: Markera allt

High signal delay: 
278 µS, 
Low signal delay:
300 µS, 
1343 µS, 
Partial data:
---------------100110101010101010010110011010100101101001101010010
010100101101010100110101010101010010110011010100101101001101010010
010100101101010100110101010101010010110011010100101101001101010010
010100101101------------------------------------------------------
Complete data:
010100101101010100110101010101010010110011010100101101001101010010
Parsed data:
0    00110000100000001101000110    0    1    00    01    0
ID: 12714822
Group: 0
Value: 1
Channel: 0
Button: 2
Användarvisningsbild
squiz3r
Inlägg: 5424
Blev medlem: 5 september 2006, 20:06:22
Ort: Lund
Kontakt:

Re: Hemautomation V2

Inlägg av squiz3r »

Det finns väl en del lib skrivna till manchester code. Borde fungera på den signalen.
Det fungerar nog inte, det är nämligen så att säga två stegs kodning. Först är det manchester kodning (att en 0'a är 01 och en 1'a 10) men sen när man väl sänder denna manchesterkodade datan så är en etta en hög puls följt av ett kort uppehåll medan en nolla är en hög puls följt av ett långt uppehåll. Sen är startbiten en hög följd av ett uppehåll av en tredje längd. För att sedan göra saker ännu knöligare så följer dimningen inte manchesterkodning utan där blir det två ettor istället vilket egentligen inte skall kunna följa varandra. Men detta är gissningsvis en efterimplementation för att kunna styra dimmers med absolut värde utan att ändra protokollet för mycket kan jag tänka mig.

Jag kollade runt en del på bibliotek som finns för styrning av nexa-mottagare osv men inget av dem föll mig i smaken. Jag testade ett av dem men det fungerade inte alls för mig, gissningsvis var det timingen som ställde till det eftersom den var ganska avvikande från det jag har mätt upp. Förövrigt allokerar många en stor array dynamiskt var gång en signal skall sändas, jag vet inte om man kan få problem med segmenterat minne då?

Kontentan blev i alla fall att jag slängde ihop ett eget bibliotek. Fungerar toppen. Har testat med mottagare från NEXA, ANSLUT och PROOVE samt dimmer från ANSLUT.

En Arduino nano v3 fick göra jobbet ihop med en liten 433Mhz sändare. kör den på 5 volt nu och har räckvidd till andra sidan av lägenheten 10 meter bor genom två betongväggar. Skulle jag vilja ha längre räckvidd går det att öka till 12 volt matning.
P_20160313_222034.jpg
Till skillnad från forumets Wiki så är dimmervärdena väldigt rakt fram på den mottagaren jag har använt i alla fall, skickar man 0 som dimmer värde är de släckta, skickar man 15 så lyser dem max. Däremellan är det bara rak interpolation. Är det någon som har bekräftat tabellen med omvandlingsfaktorn i wiki'n? Även om man inverterar nollor och ettor verkar det inte bli samma. Tycker det är konstigt att den är skriven som 1 till 16 också när det är ett 4-bit tal, varför inte 0-15?

Header:

Kod: Markera allt

/* 433 MHz transmitter for wireless wall sockets. Tested with NEXA, ANSLUT and PROOVE brands.
 * Works for on/off switches and dimmers.
 * 
 * Daniel Falk
 * DA-Robotteknik
 * +46 (0) 76-80 156 12
 * daniel @T da-robotteknik.se
 * 
 * 2016
 * You are free to use and share but please leave a acknowledge.
 * 
 */

#ifndef NEXA_H
#define NEXA_H

#include "Arduino.h"

// Define delays for different part of the signal
#define HIGH_DELAY 278 //µS
#define LOW_DELAY_ONE 300 //µS
#define LOW_DELAY_ZERO 1343 //µS
#define LOW_DELAY_START 3174 // Delay after startbit (µS)
#define LOW_DELAY_NEW_TRANSMIT 12941 // Time between repeated transmissions (µS)

// Define length of data
#define LENGTH_ID 26
#define LENGTH_GROUP 1
#define LENGTH_VALUE 1
#define LENGTH_CHANNEL 2
#define LENGTH_BUTTON 2
#define LENGTH_DIM 4

class Nexa {
  public:
    Nexa(byte txPin);

    // Send a command to put on a receiver
    void sendOn(long id, byte button);
    
    // Send a command to put off a receiver
    void sendOff(long id, byte button);
    
    // Send a command to put dimmer receiver to an absolute value (1-16)
    void sendDim(long id, byte button, byte dimValue);

  private:
    const byte txPin_;
    boolean idArray[LENGTH_ID];
    boolean groupArray[LENGTH_GROUP];
    boolean valueArray[LENGTH_VALUE];
    boolean channelArray[LENGTH_CHANNEL];
    boolean buttonArray[LENGTH_BUTTON];
    boolean dimArray[LENGTH_DIM];
    
    // Send a command to a receiver
    void sendCommand(long id, byte button, boolean value, byte dim);
    
    void setBits(boolean * dst, long number, byte length);
    
    // Sends data given in predefined arrays (with start and stop bit appended) four times
    void sendCommand(boolean dim);
    
    // Send the given data
    void sendPackage(boolean * data, byte length);
    
    // Send a complete bit (ie one -> 0,1 and zero -> 1,0)
    // value = 's' -> startbit / stopbit
    // value = '1' -> one
    // value = '0' -> zero
    void sendBit(char value);
    
    // Send raw bits
    void sendRawBit(char value);
    
    // Send a high pulse with specified on time
    void highPulse(int delayHigh);
};

#endif
Kod:

Kod: Markera allt

/* 433 MHz transmitter for wireless wall sockets. Tested with NEXA, ANSLUT and PROOVE brands.
 * Works for on/off switches and dimmers.
 * 
 * Daniel Falk
 * DA-Robotteknik
 * +46 (0) 76-80 156 12
 * daniel @T da-robotteknik.se
 * 
 * 2016
 * You are free to use and share but please leave a acknowledge.
 * 
 */

#include "Nexa.h"

Nexa::Nexa(byte txPin) : txPin_(txPin) {
  pinMode(txPin, OUTPUT);
}

// Send a command to put on a receiver
// ID = transmitter ID (0 ... 67M)
// button = 1, 2, 3 or 4
void Nexa::sendOn(long id, byte button){
  sendCommand(id, button, 1, 0);
}

// Send a command to put off a receiver
// ID = transmitter ID (0 ... 67M)
// button = 1, 2, 3 or 4
void Nexa::sendOff(long id, byte button){
  sendCommand(id, button, 0, 0);
}

// Send a command to put dimmer receiver to an absolute value
// ID = transmitter ID (0 ... 67M)
// button = 1, 2, 3 or 4
// dimValue = 0...15
void Nexa::sendDim(long id, byte button, byte dimValue){
  sendCommand(id, button, 0, dimValue);
}

// Send a command to a receiver
void Nexa::sendCommand(long id, byte button, boolean value, byte dim){
  setBits(idArray, id, LENGTH_ID);
  setBits(groupArray, 0, LENGTH_GROUP);
  setBits(valueArray, value, LENGTH_VALUE);
  setBits(channelArray, 0, LENGTH_CHANNEL);
  setBits(buttonArray, button - 1, LENGTH_BUTTON);
  setBits(dimArray, dim, LENGTH_DIM);
  sendCommand(dim!=0);
}

void Nexa::setBits(boolean * dst, long number, byte length){
  for(byte i = 1; i <= length; i++)
    dst[length-i] = number & ((long)1 << (i-1));
}

// Sends data given in predefined arrays (with start and stop bit appended) four times
void Nexa::sendCommand(boolean dim){
  for (int i = 0; i < 4; i ++){
    sendBit('s');
    sendPackage(idArray, LENGTH_ID);
    sendPackage(groupArray, LENGTH_GROUP);
    if (!dim)
      sendPackage(valueArray, LENGTH_VALUE);
    else{
      sendRawBit('1'); // Dimmer call is only case in NEXA protocol where two raw bits can have the same value..
      sendRawBit('1');
    }
    sendPackage(channelArray, LENGTH_CHANNEL);
    sendPackage(buttonArray, LENGTH_BUTTON);
    if (dim)
      sendPackage(dimArray, LENGTH_DIM);
    sendBit('s');
    delayMicroseconds(LOW_DELAY_NEW_TRANSMIT-LOW_DELAY_START);
  }    
}

// Send the given data
void Nexa::sendPackage(boolean * data, byte length){
  for (int i = 0; i < length; i ++)
    sendBit('0' + data[i]);
}

// Send a complete bit (ie one -> 0,1 and zero -> 1,0)
// value = 's' -> startbit / stopbit
// value = '1' -> one
// value = '0' -> zero
void Nexa::sendBit(char value){
  if (value == 's')
    sendRawBit(value);
  else if (value == '0'){
    sendRawBit('1');
    sendRawBit('0');
  }
  else if (value == '1'){
    sendRawBit('0');
    sendRawBit('1');
  }
}

// Send raw bits
void Nexa::sendRawBit(char value){
  if (value != 's' && value != '0' && value != '1')
    return;
  highPulse(HIGH_DELAY);
  if (value == 's')
    delayMicroseconds(LOW_DELAY_START);
  else if (value == '1')
    delayMicroseconds(LOW_DELAY_ONE);
  else if (value == '0')
    delayMicroseconds(LOW_DELAY_ZERO);
}

// Send a high pulse with specified on time
void Nexa::highPulse(int delayHigh){
  digitalWrite(txPin_, HIGH);
  delayMicroseconds(delayHigh);
  digitalWrite(txPin_, LOW);
}
Du har inte behörighet att öppna de filer som bifogats till detta inlägg.
Användarvisningsbild
squiz3r
Inlägg: 5424
Blev medlem: 5 september 2006, 20:06:22
Ort: Lund
Kontakt:

Re: Hemautomation V2

Inlägg av squiz3r »

Idag fick jag tid att fortsätta lite. Det går sakta med säkert framåt. Jag har fixat seriekommunikation nu mellan arduinon och raspberryn. Eftersom jag ganska ofta slänger ihop serieprotokoll mellan mina projekt och en dator så bestämde jag mig för att skriva ett lite mer allmänt bibliotek denna gången som jag kan återanvända i mina andra projekt. Det inkluderar synkronisering av datasträngen och en checksum så att man vet att datan som togs emot är korrekt. För att göra en ny anslutning med biblioteket är det bara kalla på konstruktorn med antalet byte som varje paket från datorn skall innehålla samt vilken baudrate man vill köra på. Kalla sedan på processBuffer(), om denna returnerar sant så finns ett nytt paket. Från detta kan man plocka ut en byte, en int eller en long från angiven/angivna index i packetet.

Kod: Markera allt

/*
 *  Class for communicating over serial port. Data received is expected to be of format
 *  's' data0 data1 ... dataN checksum
 *  where 's' is a start value and checksum is the xor of all data bytes 1..N.
 *  
 *  At correct receive and decode of data an acknowledge is sent back
 *  'a' checksum
 *  where checksum is the same as what was received.
 * 
 *  Daniel Falk
 *  DA-Robotteknik
 *  +46 (0) 76-80 156 12
 *  daniel @T da-robotteknik.se
 * 
 *  2016
 *  You are free to use and share but please leave a acknowledge.
 */

 #ifndef SERIAL_COM_H
 #define SERIAL_COM_H

 class SerialComm {
    public:
      // Set up a serial object that can receive commands with dataLength bytes.
      SerialComm(int dataLength, long baudRate);
      
      // Check if there is new data in buffer and try to decode it
      // Returns true if data package was succesfully decoded
      boolean processBuffer();
      
      // Get the i:th byte received
      byte getByte(byte i);
      
      // Get an int stored in i:th (high byte) and j:th (low byte) byte
      int getInt(byte i, byte j);
      
      // Get a long stored in i:th (highest byte) to the j:th (lowest byte) byte
      long getLong(byte i, byte j);

    private:
      byte *serialDecodeBuffer;
      byte serialDecoderBufferSize;
      byte serialDecodeBufferStart; // Index of first byte in decode buffer
      byte serialDecodeBufferStop; // Next free index in decode buffer
      boolean checksumOk;

      // Drop bytes until the first byte in decode buffer is a startbyte 
      void shiftToStart();
      
      // Pull out and remove the first byte in decode buffer
      byte popByte();
      
      // Peek on i:th byte in decode buffer (without removing it)
      byte peekByte(byte i);
      
      // Add a byte to the end of decode buffer
      void addByte(byte b);
      
      // Is decode buffer full?
      boolean decodeBufferFull();
      
      // Is decode buffer empty?
      boolean decodeBufferEmpty();
      
      // Check how many byte that is in the decode buffer
      int inBuffer();
 };

 #endif

Kod: Markera allt

/*
 *  Class for communicating over serial port. Data received is expected to be of format
 *  's' data0 data1 ... dataN checksum
 *  where 's' is a start value and checksum is the xor of all data bytes 1..N.
 *  
 *  At correct receive and decode of data an acknowledge is sent back
 *  'a' checksum
 *  where checksum is the same as what was received.
 * 
 *  Daniel Falk
 *  DA-Robotteknik
 *  +46 (0) 76-80 156 12
 *  daniel @T da-robotteknik.se
 * 
 *  2016
 *  You are free to use and share but please leave an acknowledge.
 */

 #include "SerialComm.h"

// Set up a serial object that can receive commands with dataLength bytes.
SerialComm::SerialComm(int dataLength, long baudRate){
  // Memoryspace for startbyte, data bytes and checksum
  serialDecoderBufferSize = dataLength + 2 + 1; // Buffer is one more than bytes used in transmission
  serialDecodeBuffer = new byte[serialDecoderBufferSize]; 
  serialDecodeBufferStart = 0;  // Index of first byte in buffer
  serialDecodeBufferStop = 0;  // Index of next free space in buffer
  checksumOk = 0;
  Serial.begin(baudRate);
}

// Check if there is new data in buffer and try to decode it
// Returns true if data package was succesfully decoded
boolean SerialComm::processBuffer(){
  if (checksumOk)
    popByte(); // Move to next package
  checksumOk = 0;
  shiftToStart();
  // If bytes in input buffer fill up decode buffer
  while (Serial.available() && !decodeBufferFull()){
    // Get byte from UART buffer and put in decoder buffer
    addByte(Serial.read());
    // Drop bytes if the startbyte isn't the first
    shiftToStart();
  }
  // If decode buffer is full (i.e. whole command has been received) then check the checksum and answer 
  if (decodeBufferFull()){
    byte checksum = peekByte(1);
    for (int i = 2; i < serialDecoderBufferSize - 2; i ++)
      checksum = checksum ^ peekByte(i);
    if (checksum == peekByte(serialDecoderBufferSize-2)){
      Serial.write('a');
      Serial.write(checksum);
      checksumOk = 1;
    } else {
      // Look for the next start byte
      popByte(); // Remove current start byte
      processBuffer(); // Redo process until either no more data in USART buffer or data successfully decoded
    }
  }
  return checksumOk;
}

// Get the i:th byte received
byte SerialComm::getByte(byte i){
  if (!checksumOk)
    return 0;
  return peekByte(i+1); // skip the start byte 
}

// Get an int stored in i:th (high byte) and j:th (low byte) byte
int SerialComm::getInt(byte i, byte j){
  if (!checksumOk)
    return 0;
  return ((int)peekByte(i+1))<<8 + peekByte(j+1); // skip the start byte 
}

// Get a long stored in i:th (highest byte) to the j:th (lowest byte) byte
long SerialComm::getLong(byte i, byte j){
  if (!checksumOk)
    return 0;
  long l = 0;
  signed int bytes = j-i; 
  if (bytes < 0)
    bytes = -bytes;
  bytes++; // Number of bytes is one more than diff between start and stop numbers
  byte index;
  for (int k = 0; k < bytes; k ++){
    if (i < j)
      index = i+k;
    else
      index = i-k;
    l = (l<<8) + peekByte(index+1); // Skip start byte in index calc
  }
  return l;
}

// Drop bytes until the first byte in decode buffer is a startbyte 
void SerialComm::shiftToStart() {
  while (serialDecodeBuffer[serialDecodeBufferStart] != 's' && !decodeBufferEmpty())
    popByte();
}

// Pull out and remove the first byte in decode buffer
byte SerialComm::popByte(){
  if (decodeBufferEmpty())
    return 0;
  byte b = serialDecodeBuffer[serialDecodeBufferStart];
  serialDecodeBufferStart = (serialDecodeBufferStart<serialDecoderBufferSize-1)?serialDecodeBufferStart+1:0;
  return b; 
}

// Peek on i:th byte in decode buffer (without removing it)
byte SerialComm::peekByte(byte i){
  if (inBuffer() < i)
    return 0;
  byte index = serialDecodeBufferStart + i;
  if (index > serialDecoderBufferSize-1)
    index = index - serialDecoderBufferSize;
  return serialDecodeBuffer[index];
}

// Add a byte to the end of decode buffer
void SerialComm::addByte(byte b){
  if (decodeBufferFull())
    return;
  serialDecodeBuffer[serialDecodeBufferStop] = b;
  serialDecodeBufferStop = (serialDecodeBufferStop<serialDecoderBufferSize-1)?serialDecodeBufferStop+1:0;
}

// Is decode buffer full?
boolean SerialComm::decodeBufferFull() {
  return inBuffer() >= serialDecoderBufferSize - 1;
}

// Is decode buffer empty?
boolean SerialComm::decodeBufferEmpty() {
  return inBuffer() == 0;
}

// Check how many byte that is in the decode buffer
int SerialComm::inBuffer(){
  if (serialDecodeBufferStop >= serialDecodeBufferStart)
    return serialDecodeBufferStop-serialDecodeBufferStart;
  else
    return serialDecoderBufferSize - serialDecodeBufferStart + serialDecodeBufferStop;
}
Jag skickar min data till Arduinon i detta formatet för att kunna styra mina NEXA mottagare.
rs232.jpg
Sketchen som använder mina bibliotek är enkel. Den väntar på ett kommando och skickar sedan det till NEXA mottagaren.

Kod: Markera allt

#include "Nexa.h"
#include "SerialComm.h"

#define RADIO_TRANSMITTER_PIN 13
#define SERIAL_BAUD 115200

Nexa nexa(RADIO_TRANSMITTER_PIN);
SerialComm *serialComm;

void setup() {
  // Start a new serial communication with Raspberry Pi.
  // Use 7 data bytes (plus start and checksum)
  // 4 bytes ID, 1 byte button nbr, 1 byte action command and 1 byte dim level
  serialComm = new SerialComm(7, SERIAL_BAUD);
}

void loop() {
  while(true){
    if (serialComm->processBuffer()){
      byte action = serialComm->getByte(5);
      long id = serialComm->getLong(0,3);
      byte button = serialComm->getByte(4);
      byte dim = serialComm->getByte(6);
      switch (action){
        case 0:
          nexa.sendOff(id, button);
          break;
        case 1:
          nexa.sendOn(id, button);
          break;
        case 255:
          nexa.sendDim(id, button, dim);
          break;
      }        
    }
  }
}
Du har inte behörighet att öppna de filer som bifogats till detta inlägg.
Skriv svar