diff --git a/arduino/ArduinoTellstickDuo/ArduinoTellstickDuo.ino b/arduino/ArduinoTellstickDuo/ArduinoTellstickDuo.ino new file mode 100644 index 00000000..1168aa3c --- /dev/null +++ b/arduino/ArduinoTellstickDuo/ArduinoTellstickDuo.ino @@ -0,0 +1,68 @@ +#include "usart.h" +#include "rf.h" +#include "buffer.h" +#include "config.h" + + +ISR(TIMER2_COMPA_vect) { //timer2 interrupt 16kHz. Samples the RF Rx data pin + + if (!RFRX) { //if no Radio Rx should be performed + return; + } + + uint8_t bit = digitalRead(RX_PIN); // optimized "digital_read(7)" = "PIND & 0x1" + + //will store "lows" on even buffer addresses and "highs" on odd buffer addresses + if ( ( ((int)bufferWriteP) & 0x1 ) != bit ) { //compare the bit and the buffer pointer address. true if one is odd and one is even. + //step the buffer pointer + if ( bufferWriteP + 1 > RF_rxBufferEndP) { + bufferWriteP = RF_rxBufferStartP; + } else { + ++bufferWriteP; + } + *bufferWriteP = 1; //reset and step once on this addess + } else { + if (*bufferWriteP < 255) { //only values up to 255 + ++(*bufferWriteP); //step the buffer + } + } + +};//end timer2 interrupt + + +void setup() { + + Serial.begin(9600); + + pinMode(RX_PIN, INPUT); //set RX pin to input + pinMode(TX_PIN, OUTPUT); //ser TX pin as output + + //setup timer2 interrupt at 16kHz for RF sampling + cli();//stop interrupts + TCCR2A = 0;// set entire TCCR2A register to 0 + TCCR2B = 0;// same for TCCR2B + TCNT2 = 0;//initialize counter value to 0 + OCR2A = 124;// = ( (16*10^6) / (16000Hz*8pc) ) - 1 (must be <256) + TCCR2A |= (1 << WGM21); // turn on CTC mode + TCCR2B |= (1 << CS21); // Set CS21 bit for 8 prescaler + TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt + sei();//allow interrupts + + //reset buffer just to be sure + for (uint8_t* p = RF_rxBufferStartP; p <= RF_rxBufferEndP; ++p) { + *p = 0; + } + + RFRX = true; //start receiving radio data + +};//end setup + +void loop() { + + //Receive and execute command over serial + parseSerialForCommand(); + + //Receive signal over air and send it over serial + parseRadioRXBuffer(); + +};//end loop diff --git a/arduino/ArduinoTellstickDuo/archtech.cpp b/arduino/ArduinoTellstickDuo/archtech.cpp new file mode 100644 index 00000000..38a65ede --- /dev/null +++ b/arduino/ArduinoTellstickDuo/archtech.cpp @@ -0,0 +1,50 @@ +#include "archtech.h" +#include "buffer.h" + +bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP) { //start points to a "one" buffer byte, end points to a "zero" buffer byte + uint64_t data = 0; + bool dimValuePresent = false; + + for (uint8_t i = 0; i < 31; ++i) { + + if (calculateBufferPointerDistance(bufStartP, bufEndP) < 4) { //less than 4 more high/low rto read in buffer + return false; + } + + uint8_t b1 = *bufStartP; //no of high + stepBufferPointer(&bufStartP); + uint8_t b2 = *bufStartP; //no of low + stepBufferPointer(&bufStartP); + uint8_t b3 = *bufStartP; //no of high + stepBufferPointer(&bufStartP); + uint8_t b4 = *bufStartP; //no of low + stepBufferPointer(&bufStartP); + + //TODO: add support for absolute dim values + + if (ARCHTECH_LOW_LOW <= b1 && b1 <= ARCHTECH_LOW_HIGH && + ARCHTECH_LOW_LOW <= b2 && b2 <= ARCHTECH_LOW_HIGH && + ARCHTECH_LOW_LOW <= b3 && b3 <= ARCHTECH_LOW_HIGH && + ARCHTECH_HIGH_LOW <= b4 && b4 <= ARCHTECH_HIGH_HIGH) { //"one" is sent over air + data <<= 1; //shift in a zero + data |= 0x1; //add one + } else if (ARCHTECH_LOW_LOW <= b1 && b1 <= ARCHTECH_LOW_HIGH && + ARCHTECH_HIGH_LOW <= b2 && b2 <= ARCHTECH_HIGH_HIGH && + ARCHTECH_LOW_LOW <= b3 && b3 <= ARCHTECH_LOW_HIGH && + ARCHTECH_LOW_LOW <= b4 && b4 <= ARCHTECH_LOW_HIGH) { //"zero" is sent over air + data <<= 1; //shift in a zero + } else { + return false; + } + + } + + Serial.print(F("+Wclass:command;protocol:arctech;model:selflearning;data:0x")); + uint8_t hexToSend = (dimValuePresent ? 9 : 8); + for (int8_t i = hexToSend - 1; i >= 0; --i) { + Serial.print( (byte)((data >> (4 * i)) & 0x0F), HEX); + } + Serial.println(F(";")); + + return true; +}; //end parseArctechSelfLearning diff --git a/arduino/ArduinoTellstickDuo/archtech.h b/arduino/ArduinoTellstickDuo/archtech.h new file mode 100644 index 00000000..9acd8332 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/archtech.h @@ -0,0 +1,13 @@ +#ifndef ARCHTECH_H +#define ARCHTECH_H + +#include "Arduino.h" + +#define ARCHTECH_LOW_LOW 2 //a "low" is defined as at least this many "low" samples in a row +#define ARCHTECH_LOW_HIGH 7 //a "low" is defined as at most this many "low" samples in a row +#define ARCHTECH_HIGH_LOW 17 //a "high" is defined as at least this many "high" samples in a row +#define ARCHTECH_HIGH_HIGH 23 //a "high" is defined as at most this many "high" samples in a row + +bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP); + +#endif //ARCHTECH_H \ No newline at end of file diff --git a/arduino/ArduinoTellstickDuo/buffer.cpp b/arduino/ArduinoTellstickDuo/buffer.cpp new file mode 100644 index 00000000..b13e30bf --- /dev/null +++ b/arduino/ArduinoTellstickDuo/buffer.cpp @@ -0,0 +1,26 @@ +#include "buffer.h" + +uint8_t RF_rxBuffer[512]; //must have and even number of elements +uint8_t* RF_rxBufferStartP = &RF_rxBuffer[0]; +uint8_t* RF_rxBufferEndP = &RF_rxBuffer[511]; +volatile uint8_t* bufferWriteP = RF_rxBufferStartP; + +uint16_t calculateBufferPointerDistance(uint8_t* bufStartP, uint8_t* bufEndP) { + if (bufStartP <= bufEndP) { + return bufEndP - bufStartP + 1; + } else { + return (RF_rxBufferEndP - bufStartP) + (bufEndP - RF_rxBufferStartP) + 2; + } +}; //end calculateBufferPointerDistance + +uint8_t* getNextBufferPointer(uint8_t* p) { + if ( p + 1 > RF_rxBufferEndP) { + return RF_rxBufferStartP; + } else { + return p + 1; + } +}; //end getNextBufferPointer + +void stepBufferPointer(uint8_t** p) { + *p = getNextBufferPointer(*p); +}; //end stepBufferPointer diff --git a/arduino/ArduinoTellstickDuo/buffer.h b/arduino/ArduinoTellstickDuo/buffer.h new file mode 100644 index 00000000..7bd1dc30 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/buffer.h @@ -0,0 +1,15 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include "Arduino.h" + +uint16_t calculateBufferPointerDistance(uint8_t* bufStartP, uint8_t* bufEndP); +uint8_t* getNextBufferPointer(uint8_t* p); +void stepBufferPointer(uint8_t** p); + +extern uint8_t RF_rxBuffer[512]; //must have and even number of elements +extern uint8_t* RF_rxBufferStartP; +extern uint8_t* RF_rxBufferEndP; +extern volatile uint8_t* bufferWriteP; + +#endif //BUFFER_H diff --git a/arduino/ArduinoTellstickDuo/config.h b/arduino/ArduinoTellstickDuo/config.h new file mode 100644 index 00000000..08c9825c --- /dev/null +++ b/arduino/ArduinoTellstickDuo/config.h @@ -0,0 +1,7 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define RX_PIN 7 +#define TX_PIN 8 + +#endif //CONFIG_H \ No newline at end of file diff --git a/arduino/ArduinoTellstickDuo/oregon protocol specification.txt b/arduino/ArduinoTellstickDuo/oregon protocol specification.txt new file mode 100644 index 00000000..9c761af1 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/oregon protocol specification.txt @@ -0,0 +1,29 @@ +SIGNAL +signal i s two times in a row with a "low" pause of 8192us between them + +The message buffer is 9 bytes long + +Each signal starts with a preampble, the data and a postamble + +PREAMBLE +consists of two "one"-bytes + +DATA +9 bytes + +POSTAMBLE +consists of one "zero"-byte + +A "one" bit is sent over the air as: +high for 512us +low for 1024us +high for 512us + +A "zero" bit is sent over the air as: +low for 512us +high for 1024us +low for 512us + +1 byte = 8*(512+512+1024)us = 16384us +one signal = 2+9+1 bytes = 12 bytes = 196608us +signal + 8192 + signal = 401408us = 401.408ms diff --git a/arduino/ArduinoTellstickDuo/rf.cpp b/arduino/ArduinoTellstickDuo/rf.cpp new file mode 100644 index 00000000..2ed0d6a5 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/rf.cpp @@ -0,0 +1,100 @@ +#include "rf.h" +#include "buffer.h" +#include "archtech.h" +#include "config.h" + +volatile bool RFRX = false; + +void parseRadioRXBuffer() { + static uint8_t* bufferReadP = RF_rxBufferStartP; + static uint8_t* startDataP = 0; //will always point to a "high" buffer address + static uint8_t* endDataP = 0; //will always point to a "low" buffer address + static uint8_t prevValue = 0; //contains the value of the previous buffer index read + + bool parse = false; + while (bufferReadP != bufferWriteP) { //stop if the read pointer is pointing to where the writing is currently performed + + if ( (((int)bufferReadP) & 0x1) == 1 ) { //buffer pointer is odd (stores highs) + if (prevValue >= SILENCE_LENGTH) { + startDataP = bufferReadP; //some new data must starrt here since this is the first "high" after a silent period + } + } else { //buffer pointer is even (stores lows) + if (*bufferReadP >= SILENCE_LENGTH) { //evaluate if it is time to parse the curernt data + endDataP = bufferReadP; //this is a silient period and must be the end of a data + parse = true; + break; + } + } + + uint8_t* nextBufferReadP = getNextBufferPointer(bufferReadP); + if (nextBufferReadP == startDataP) { //next pointer will point to startDataP. Data will overflow. Reset the data pointers. + startDataP = 0; + endDataP = 0; + } + + //advance buffer pointer one step + bufferReadP = nextBufferReadP; + + prevValue = *bufferReadP; //update previous value + } + + if (!parse) { + return; + } + + if (startDataP == 0 || endDataP == 0) { + return; + } + + /* + * At this point the startDataP will point to the first high after a silent period + * and the endDataP will point at the first (low) silent period after the data data start. + */ + + //make sure that the data set size is big enought to parse. + uint16_t dataSetSize = calculateBufferPointerDistance(startDataP, endDataP); + if (dataSetSize < 32) { //at least 32 low/high + return; + } + + //Let all available parsers parse the data set now. + parseArctechSelfLearning(startDataP, endDataP); + //TODO: add more parsers here + +}; //end radioTask + +void sendTCodedData(uint8_t* data, uint8_t T_long, uint8_t* timings, uint8_t repeat, uint8_t pause) { + RFRX = false; //turn off the RF reciever + for (uint8_t rep = 0; rep < repeat; ++rep) { + bool nextPinState = HIGH; + for (int i = 0; i < T_long; ++i) { + uint8_t timeIndex = (data[i / 4] >> (6 - (2 * (i % 4)))) & 0x03; + if (timings[timeIndex] > 0 || i == T_long - 1) { + digitalWrite(TX_PIN, nextPinState); + delayMicroseconds(10 * timings[timeIndex]); + } + nextPinState = !nextPinState; + } + digitalWrite(TX_PIN, LOW); + if (rep < repeat - 1) { + delay(pause); + } + } + RFRX = true; //turn on the RF reciever +}; + +void sendSCodedData(uint8_t* data, uint8_t pulseCount, uint8_t repeat, uint8_t pause) { + RFRX = false; //turn off the RF reciever + for (uint8_t rep = 0; rep < repeat; ++rep) { + bool nextPinState = HIGH; + for (int i = 0; i < pulseCount; ++i) { + if (data[i] > 0 || i == pulseCount - 1) { + digitalWrite(TX_PIN, nextPinState); + delayMicroseconds(data[i] * 10); + } + nextPinState = !nextPinState; + } + delay(pause); + } + RFRX = false; //turn on the RF reciever +}; diff --git a/arduino/ArduinoTellstickDuo/rf.h b/arduino/ArduinoTellstickDuo/rf.h new file mode 100644 index 00000000..a77a2b50 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/rf.h @@ -0,0 +1,14 @@ +#ifndef RF_H +#define RF_H + +#include "Arduino.h" + +#define SILENCE_LENGTH 100 //the number of samples with "low" that represents a silent period between two signals + +void parseRadioRXBuffer(); +void sendTCodedData(uint8_t* data, uint8_t T_long, uint8_t* timings, uint8_t repeat, uint8_t pause); +void sendSCodedData(uint8_t* data, uint8_t pulseCount, uint8_t repeat, uint8_t pause); + +extern volatile bool RFRX; + +#endif //RF_H diff --git a/arduino/ArduinoTellstickDuo/usart.cpp b/arduino/ArduinoTellstickDuo/usart.cpp new file mode 100644 index 00000000..f313a473 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/usart.cpp @@ -0,0 +1,106 @@ +#include "usart.h" +#include "rf.h" + +uint8_t Serial_rxBuffer[79]; + +void parseSerialForCommand() { + if (Serial.available() > 0) { + uint8_t rxDataSize = Serial.readBytesUntil('+', &Serial_rxBuffer[0], 79); + if (rxDataSize > 0) { + parseRxBuffer(&Serial_rxBuffer[0], 0, rxDataSize, false, 3, 0); + } + } +}; //end serialTask + +bool parseRxBuffer(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause) { + if (startIndex > endIndex) { + return false; + } + char c = buffer[startIndex]; + //Serial.print("DEBUG: char:"); Serial.println(c, DEC); + switch (c) { + case 'S': + return handleSCommand(buffer, startIndex + 1, endIndex, debug, repeat, pause); + case 'T': + return handleTCommand(buffer, startIndex + 1, endIndex, debug, repeat, pause); + case 'V': + Serial.println(F("+V2")); + return parseRxBuffer(buffer, startIndex + 1, endIndex, debug, repeat, pause); + case 'D': + return parseRxBuffer(buffer, startIndex + 1, endIndex, !debug, repeat, pause); + case 'P': + if (endIndex - startIndex + 1 < 3) { + return false; + } //at least {'P',[p-value],'+'} must be left in the buffer + return parseRxBuffer(buffer, startIndex + 2, endIndex, debug, repeat, buffer[startIndex + 1]); + case 'R': + if (endIndex - startIndex + 1 < 3) { + return false; + } //at least {'R',[r-value],'+'} must be left in the buffer + return parseRxBuffer(buffer, startIndex + 2, endIndex, debug, buffer[startIndex + 1], pause); + case '+': + return true; + default: + //Serial.print("DEBUG: unknown char: '"); Serial.print(c, BIN); Serial.println("'"); + return false; + } +}; //end parseRxBuffer + +bool handleSCommand(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause) { + //Parse message received from serial + uint8_t S_data[78]; //78 pulses + uint8_t pulseCount = 0; + for (uint8_t i = startIndex; i <= endIndex; ++i) { + if (buffer[i] == '+') { + break; + } else if (i == endIndex) { + return false; + } else { + S_data[pulseCount++] = buffer[i]; + } + } + //Send message + sendSCodedData(&S_data[0], pulseCount, repeat, pause); + + //send confirmation over serial + Serial.println(F("+S")); + return true; +}; //end handleS + +bool handleTCommand(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause) { + //Parse message received from serial + uint8_t T_data[72]; //0-188 pulses + if (endIndex - startIndex < 5) { + //Serial.println("DEBUG: wrong size!"); + return false; + } + uint8_t buff_p = startIndex; + uint8_t T_times[4] = {buffer[buff_p++], buffer[buff_p++], buffer[buff_p++], buffer[buff_p++]}; + uint8_t T_long = buffer[buff_p++]; + uint8_t T_bytes = 0; + if ( (T_long / 4.0) > (float)(T_long / 4) ) { + T_bytes = T_long / 4 + 1; + } else { + T_bytes = T_long / 4; + } + uint8_t j = 0; + while (j < T_bytes) { + if (buffer[buff_p] == '+') { + break; + } else if (buff_p >= endIndex) { + return false; + } else { + T_data[j++] = buffer[buff_p++]; + } + } + if ( j != T_bytes ) { + return false; + } + + //Send message + sendTCodedData(&T_data[0], T_long, &T_times[0], repeat, pause); + + //send confirmation over serial + Serial.println(F("+T")); + return parseRxBuffer(buffer, buff_p, endIndex, debug, repeat, pause); +}; //end handleT diff --git a/arduino/ArduinoTellstickDuo/usart.h b/arduino/ArduinoTellstickDuo/usart.h new file mode 100644 index 00000000..b397ff19 --- /dev/null +++ b/arduino/ArduinoTellstickDuo/usart.h @@ -0,0 +1,13 @@ +#ifndef USART_H +#define USART_H + +#include "Arduino.h" + +void parseSerialForCommand(); +bool parseRxBuffer(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause); +bool handleSCommand(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause); +bool handleTCommand(byte* buffer, uint8_t startIndex, uint8_t endIndex, bool debug, uint8_t repeat, uint8_t pause); + +extern uint8_t Serial_rxBuffer[79]; + +#endif //USART_H