Class to communicate with ELV(R) MAX! wireless devices with RFM22B-Modules. Based on Library RF22. Initial version unable to send! Only receive! See http://mbed.org/users/charly/notebook/reading-a-max-wireless-window-sensor-with-rfm22-an/

Dependents:   RF22_MAX_test_Send

RF22Max.cpp

Committer:
charly
Date:
2013-10-22
Revision:
2:f75e51ce001b
Parent:
0:565a81d6f278

File content as of revision 2:f75e51ce001b:

//show debug output on Serial pc
#define DEBUG

#include "mbed.h"
#include <RF22Max.h>


RF22Max::RF22Max(PinName slaveSelectPin, PinName mosi, PinName miso, PinName sclk, PinName interrupt)
    : RF22(slaveSelectPin , mosi, miso, sclk, interrupt )
{
}

static const RF22::ModemConfig max_config = { // for MAX! protocol
    .reg_1c = 0x01, //POR-Value : 75KHzBW?
    .reg_1f = 0x03, //POR-Value: 
    .reg_20 = 0x90, //?? Oversampling Rate
    .reg_21 = 0x20,
    .reg_22 = 0x51,
    .reg_23 = 0xea,
    .reg_24 = 0x00,
    .reg_25 = 0x58,
    // 2c - 2e are only for OOK
    .reg_2c = 0x00,
    .reg_2d = 0x00,
    .reg_2e = 0x00,
    .reg_58 = 0x80, // Copied from RF22 defaults
    .reg_69 = 0x60, // Copied from RF22 defaults
    //.reg_6e = 0x08, // TX Data Rate 1
    //.reg_6f = 0x31, // TX Data Rate 0
    .reg_6e = 0x51, // TX Data Rate 1
    .reg_6f = 0xDC, // TX Data Rate 0
    .reg_70 = 0x20, //<30kps,no manchaster, no dewithening
    .reg_71 = RF22_DTMOD_FIFO | RF22_MODTYP_FSK, //=0x22
    .reg_72 = 0x1e,
};


/* Sync words to send / check for. Don't forget to update RF22_SYNCLEN
* below if changing the length of this array. */

const uint8_t max_sync_words[] = {
    0xc6,
    0x26,
    0xc6,
    0x26,
};


enum modes {MODE_AUTO, MODE_MANUAL, MODE_TEMPORARY, MODE_BOOST};

const char *mode_str[] = {
    [MODE_AUTO] = "auto",
    [MODE_MANUAL] = "manual",
    [MODE_TEMPORARY] = "temporary",
    [MODE_BOOST] = "boost"
};

char *type_str(uint8_t type)
{
    switch(type) {
        case 0x00:
            return "PairPing";
        case 0x01:
            return "PairPong";
        case 0x02:
            return "Ack";
        case 0x03:
            return "TimeInformation";
        case 0x10:
            return "ConfigWeekProfile";
        case 0x11:
            return "ConfigTemperatures";
        case 0x12:
            return "ConfigValve";
        case 0x20:
            return "AddLinkPartner";
        case 0x21:
            return "RemoveLinkPartner";
        case 0x22:
            return "SetGroupId";
        case 0x23:
            return "RemoveGroupId";
        case 0x30:
            return "ShutterContactState";
        case 0x40:
            return "SetTemperature";
        case 0x42:
            return "WallThermostatState";
        case 0x43:
            return "SetComfortTemperature";
        case 0x44:
            return "SetEcoTemperature";
        case 0x50:
            return "PushButtonState";
        case 0x60:
            return "ThermostatState";
        case 0x82:
            return "SetDisplayActualTemperature";
        case 0xF1:
            return "WakeUp";
        case 0xF0:
            return "Reset";
    }
    return "Unknown";
};


/* First 255 bytes of PN9 sequence used for data whitening by the CC1101
 chip. The RF22 chip is documented to support the same data whitening
 algorithm, but in practice seems to use a different sequence.

 Data was generated using the following python snippet:

import itertools
def pn9(state):
    while True:
        yield hex(state & 0xff)
        # The pn9 generator is clocked 8 times while shifting in the
        # next data byte
        for i in range(8):
            state = (state >> 1) + (((state & 1) ^ (state >> 5) & 1) << 8)
print(list(itertools.islice(pn9(0x1ff), 255)))
*/

const uint8_t pn9[] = {
    0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24,
    0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a,
    0x54, 0x7d, 0x2d, 0xd8, 0x6d, 0x0d, 0xba, 0x8f,
    0x67, 0x59, 0xc7, 0xa2, 0xbf, 0x34, 0xca, 0x18,
    0x30, 0x53, 0x93, 0xdf, 0x92, 0xec, 0xa7, 0x15,
    0x8a, 0xdc, 0xf4, 0x86, 0x55, 0x4e, 0x18, 0x21,
    0x40, 0xc4, 0xc4, 0xd5, 0xc6, 0x91, 0x8a, 0xcd,
    0xe7, 0xd1, 0x4e, 0x09, 0x32, 0x17, 0xdf, 0x83,
    0xff, 0xf0, 0x0e, 0xcd, 0xf6, 0xc2, 0x19, 0x12,
    0x75, 0x3d, 0xe9, 0x1c, 0xb8, 0xcb, 0x2b, 0x05,
    0xaa, 0xbe, 0x16, 0xec, 0xb6, 0x06, 0xdd, 0xc7,
    0xb3, 0xac, 0x63, 0xd1, 0x5f, 0x1a, 0x65, 0x0c,
    0x98, 0xa9, 0xc9, 0x6f, 0x49, 0xf6, 0xd3, 0x0a,
    0x45, 0x6e, 0x7a, 0xc3, 0x2a, 0x27, 0x8c, 0x10,
    0x20, 0x62, 0xe2, 0x6a, 0xe3, 0x48, 0xc5, 0xe6,
    0xf3, 0x68, 0xa7, 0x04, 0x99, 0x8b, 0xef, 0xc1,
    0x7f, 0x78, 0x87, 0x66, 0x7b, 0xe1, 0x0c, 0x89,
    0xba, 0x9e, 0x74, 0x0e, 0xdc, 0xe5, 0x95, 0x02,
    0x55, 0x5f, 0x0b, 0x76, 0x5b, 0x83, 0xee, 0xe3,
    0x59, 0xd6, 0xb1, 0xe8, 0x2f, 0x8d, 0x32, 0x06,
    0xcc, 0xd4, 0xe4, 0xb7, 0x24, 0xfb, 0x69, 0x85,
    0x22, 0x37, 0xbd, 0x61, 0x95, 0x13, 0x46, 0x08,
    0x10, 0x31, 0x71, 0xb5, 0x71, 0xa4, 0x62, 0xf3,
    0x79, 0xb4, 0x53, 0x82, 0xcc, 0xc5, 0xf7, 0xe0,
    0x3f, 0xbc, 0x43, 0xb3, 0xbd, 0x70, 0x86, 0x44,
    0x5d, 0x4f, 0x3a, 0x07, 0xee, 0xf2, 0x4a, 0x81,
    0xaa, 0xaf, 0x05, 0xbb, 0xad, 0x41, 0xf7, 0xf1,
    0x2c, 0xeb, 0x58, 0xf4, 0x97, 0x46, 0x19, 0x03,
    0x66, 0x6a, 0xf2, 0x5b, 0x92, 0xfd, 0xb4, 0x42,
    0x91, 0x9b, 0xde, 0xb0, 0xca, 0x09, 0x23, 0x04,
    0x88, 0x98, 0xb8, 0xda, 0x38, 0x52, 0xb1, 0xf9,
    0x3c, 0xda, 0x29, 0x41, 0xe6, 0xe2, 0x7b
};

#ifdef DEBUG
void RF22Max::printHex(uint8_t *buf, size_t len, bool nl)
{
    for (size_t i = 0; i < len; i++) {
        pc.printf("%02X ",buf[i]);
    }
    if (nl)
        pc.printf("\n\r");
}
#endif

boolean RF22Max::init()
{
    boolean ret = this->RF22::init();
    if (ret) {
        //Max! specific settings
        this->RF22::setModemRegisters(&max_config);
        this->RF22::setFrequency(868.299866, 0.035);
        /* Disable TX packet control, since the RF22 doesn't do proper
         * whitening so can't read the length header or CRC. We need RX packet
         * control so the RF22 actually sends pkvalid interrupts when the
         * manually set packet length is reached. */
        this->RF22::spiWrite(RF22_REG_30_DATA_ACCESS_CONTROL, RF22_MSBFRST | RF22_ENPACRX);
        /* No packet headers, 4 sync words, fixed packet length */
        this->RF22::spiWrite(RF22_REG_32_HEADER_CONTROL1, RF22_BCEN_NONE | RF22_HDCH_NONE);
        this->RF22::spiWrite(RF22_REG_33_HEADER_CONTROL2, RF22_HDLEN_0 | RF22_FIXPKLEN | RF22_SYNCLEN_4);
        this->RF22::setSyncWords(max_sync_words, lengthof(max_sync_words));
        /* Detect preamble after 4 nibbles */
        this->RF22::spiWrite(RF22_REG_35_PREAMBLE_DETECTION_CONTROL1, (0x4 << 3));
        /* Send 4 nibbles of preamble */
        this->RF22::setPreambleLength(4); // in nibbles
        this->RF22::spiWrite(RF22_REG_3E_PACKET_LENGTH, 30); // maximum length of a MAX!-packet
    }
    return ret;
}

/*
 CRC code based on example from Texas Instruments DN502, matches
 CC1101 implementation
*/
#define CRC16_POLY 0x8005
uint16_t RF22Max::calc_crc_step(uint8_t crcData, uint16_t crcReg)
{
    uint8_t i;
    for (i = 0; i < 8; i++) {
        if (((crcReg & 0x8000) >> 8) ^ (crcData & 0x80))
            crcReg = (crcReg << 1) ^ CRC16_POLY;
        else
            crcReg = (crcReg << 1);
        crcData <<= 1;
    }
    return crcReg;
} // culCalcCRC

#define CRC_INIT 0xFFFF
uint16_t RF22Max::calc_crc(uint8_t *buf, size_t len)
{
    uint16_t checksum;
    checksum = CRC_INIT;
    // Init value for CRC calculation
    for (size_t i = 0; i < len; i++)
        checksum = calc_crc_step(buf[i], checksum);
    return checksum;
}


boolean RF22Max::recv(uint8_t* buf, uint8_t* len)
{

    if (RF22::recv(buf, len)) {
        *len = 50;  // limit message to 50 Bytes as device receives all 255 Bytes
        
        /* Dewhiten data */
        for (int i = 0; i < *len; i++)
            buf[i] ^= pn9[i];

        // now read the real length
        *len = buf[0]+3; // 1 length-Byte + 2 CRC

        if (*len < 3 || *len > lengthof(pn9)) {
#ifdef DEBUG        
            printHex(buf, *len, true);
#endif
            return false;
        }
#ifdef DEBUG        
        printHex(buf, *len, true);
#endif        
        return true;
    } else
        return false;
}

boolean RF22Max::recv_max(RF22Max::max_message* message)
{
    uint8_t buf[RF22_MAX_MESSAGE_LEN];
    
    uint8_t len = sizeof(buf);

    message->len = 0;

    if (recv(buf,&len)) {

        /* Calculate CRC (but don't include the CRC itself) */
        uint16_t crc = calc_crc(buf, len - 2);
        if (buf[len - 1] != (crc & 0xff) || buf[len - 2] != (crc >> 8)) {

            return false;
        }

        /* Don't use the CRC as data */
        len -= 2;

        message->len     = len;                   //message-length
        message->cnt     = buf[1];                //message-counter
        message->flags   = buf[2];
        message->type    = buf[3];
        strcpy(message->type_str,type_str(message->type));
        message->frm_adr = buf[4]<<16 | buf[5]<<8| buf[6];            // unique address of device
        message->to_adr  = buf[7]<<16 | buf[8]<<8| buf[9]; ;             // unique address of device
        message->groupid = buf[10];            //groupid
        memcpy( (void *) message->payload, (void *) buf[11],len-11);               // data
        message->crc     = buf[len-2]<<8 | buf[len-1];                // crc for the message

        if (message->type == 0x30 && len >= 11) { //ShutterContactState
            bool baterry_low = (buf[11] >> 7) & 0x1;
            bool state = (buf[11]>>1) & 0x1;

            if (state) {
                strcpy(message->state,"open");
            } else {
                strcpy(message->state,"closed");
            }

            if (baterry_low) {
                strcpy(message->battery_state,"low");
            } else {
                strcpy(message->battery_state,"good");
            }
        }

        if (message->type == 0x50 && len >= 11) { //PushButtonState
            bool baterry_low = (buf[11] >> 7) & 0x1;    // to validate!!!
            bool state = (buf[12]) & 0x1;

            if (state) {
                strcpy(message->state,"auto");
            } else {
                strcpy(message->state,"eco");
            }

            if (baterry_low) {
                strcpy(message->battery_state,"low");
            } else {
                strcpy(message->battery_state,"good");
            }

        }
#if 0
        if (message->type == 0x00 && len >= 11) { //PairPing
            char    serial[20]="";
            uint8_t sbuf[RF22_MAX_MESSAGE_LEN];
            uint8_t slen =0;
            
            strncpy(serial,(char*)buf+14,10); //10 Characters for Seial Number
            serial[11] = '\0';
#ifdef DEBUG                    
            pc.printf("Serial:        %s\n\r",serial);
#endif            

            // try to send PairPong
            // wait some time
            wait_ms(20);         // seen at moritz-code
            sbuf[0] = 11; // MsgLen
            sbuf[1] = buf[1]+1 &0xFF; // MsgCount ??
            sbuf[2] = 0x00; // Flag
            sbuf[3] = 0x01; // Type = Cmd = PairPong
            sbuf[4] = 0x11; // From Fake Address
            sbuf[5] = 0x11; // From
            sbuf[6] = 0x11; // From
            sbuf[7] = buf[4] ; // To Address = From address of Windowcontact
            sbuf[8] = buf[5] ;
            sbuf[9] = buf[6] ;
            sbuf[10] = 0x00; // GroupId
            sbuf[11] = 0x00;  //Payload is 0x00 for pairpong?
            slen = 12+2; //+2Byte CRC????
            /* Calculate CRC */
            uint16_t scrc = calc_crc(sbuf, slen - 2);
            sbuf[12] = crc >> 8;
            sbuf[13] = crc & 0xff;


            if (RF22::send(sbuf,slen)) {
#ifdef DEBUG        
                pc.printf("Send PairPong OK\n\r");
#endif                
            } else {
#ifdef DEBUG                    
                pc.printf("Send PairPong NOT OK\n\r");
#endif                
            }

        }
#endif
        return true;
    } else
        return false;
}