diff --git a/emulator/iSkipper/examples/capture/capture.ino b/emulator/iSkipper/examples/capture/capture.ino index 4a18d5e..b3a9c66 100644 --- a/emulator/iSkipper/examples/capture/capture.ino +++ b/emulator/iSkipper/examples/capture/capture.ino @@ -30,7 +30,12 @@ void setup() void loop() { char tmp[50]; - iClickerPacket r; + iClickerPacket r; + + Serial.println("Sending welcome message"); + clicker.sendWelcomePacket("_0+2-2=x_?", QuestionModes::MULTIPLE_NUMERIC, 5); + clicker.setChannel(iClickerChannels::AA); + clicker.startPromiscuous(CHANNEL_SEND, recvPacketHandler); //see if there is a pending packet, check if its an answer packet while (recvBuf.pull(&r) && r.type == PACKET_ANSWER) { diff --git a/emulator/iSkipper/iClickerEmulator.cpp b/emulator/iSkipper/iClickerEmulator.cpp index 4e1adb9..e994dcd 100644 --- a/emulator/iSkipper/iClickerEmulator.cpp +++ b/emulator/iSkipper/iClickerEmulator.cpp @@ -225,6 +225,62 @@ void iClickerEmulator::acknowledgeAnswer(iClickerAnswerPacket* packet, bool acce _radio.send(ack_payload, 5, false); } +// Returns 0 on failure, otherwise converts the ascii character `c` +// to the equivilent fucky-iClicker-text-encoding +uint8_t asciiToiClickerEncoding(char c) { + if (c >= 'a' && c <= 'z') { + return (c - 'a') + 0x8B; + } else if (c >= 'A' && c <= 'Z') { + return (c - 'A') + 0x8B; + } else if (c >= '1' && c <= '9') { + return (c - '1') + 0x81; + } + switch (c) { + case '0': return 0x8A; + case '-': return 0xA5; + case '+': return 0xA6; + case '=': return 0xA7; + case '?': return 0xA8; + case '_': return 0xA9; + default: return 0; + } +} + +void iClickerEmulator::sendWelcomePacket(char* msg, QuestionMode mode, uint16_t num_questions) { + uint8_t welcome_payload[WELCOME_PACKET_SIZE]; + memset(welcome_payload, 0, WELCOME_PACKET_SIZE); + + if (msg != NULL) { + size_t length = strlen(msg); + // Welcome message can't be longer than 8 chars + if (length > 8) { + length = 8; + } + + for (size_t i = 0; i < length; i++) { + uint8_t mapped_character = asciiToiClickerEncoding(msg[i]); + welcome_payload[i] = mapped_character; + } + } + + // set the magic bytes which tell what question mode to put iClicker2s in + welcome_payload[8] = mode.welcome_bytes[0]; + welcome_payload[11] = mode.welcome_bytes[1]; + // num_questions only matters in the MULTIPLE_* modes + uint8_t* num_question_bytes = (uint8_t*) &num_questions; + welcome_payload[9] = num_question_bytes[0]; + welcome_payload[10] = num_question_bytes[1]; + // compute the checksum + uint8_t checksum = 0; + for (int i = 0; i < WELCOME_PACKET_SIZE - 1; i++) { + checksum += welcome_payload[i]; + } + welcome_payload[WELCOME_PACKET_SIZE - 1] = checksum; + + configureRadio(CHANNEL_RECV_WELCOME, WELCOME_RECV_SYNC_ADDR); + _radio.send(welcome_payload, 16, true); +} + void iClickerEmulator::setChannel(iClickerChannel chan) { @@ -242,8 +298,20 @@ iClickerChannel iClickerEmulator::getChannel() void iClickerEmulator::configureRadio(iClickerChannelType type, const uint8_t *syncaddr) { + int syncAddrLength = 0; + switch (type) { + case CHANNEL_SEND: + syncAddrLength = SEND_SYNC_ADDR_LEN; + break; + case CHANNEL_RECV: + syncAddrLength = RECV_SYNC_ADDR_LEN; + break; + case CHANNEL_RECV_WELCOME: + syncAddrLength = WELCOME_RECV_SYNC_ADDR_LEN; + break; + } // set the correct sync addr and len - _radio.setSyncAddr(syncaddr, type == CHANNEL_SEND ? SEND_SYNC_ADDR_LEN : RECV_SYNC_ADDR_LEN); + _radio.setSyncAddr(syncaddr, syncAddrLength); // put radio on the correct freq and packet size _radio.setChannelType(type); diff --git a/emulator/iSkipper/iClickerEmulator.h b/emulator/iSkipper/iClickerEmulator.h index d065038..9e4bfdb 100644 --- a/emulator/iSkipper/iClickerEmulator.h +++ b/emulator/iSkipper/iClickerEmulator.h @@ -23,6 +23,44 @@ enum iClickerAnswer : uint8_t NUM_ANSWER_CHOICES, }; +struct QuestionMode +{ + // magic bytes used in the welcome packet for this + // question mode + uint8_t welcome_bytes[2]; +}; + +namespace QuestionModes { + const QuestionMode MULTIPLE_CHOICE = + { + {0x92, 0x62} + }; + + const QuestionMode NUMERIC = + { + {0x93, 0x63} + }; + + const QuestionMode ALPHANUMERIC = + { + {0x9B, 0x6B} + }; + + const QuestionMode MULTIPLE_NUMERIC = + { + {0x94, 0x64} + }; + + const QuestionMode MULTIPLE_ALPHANUMERIC = + { + {0x9C, 0x6C} + }; +}; + +#define WELCOME_RECV_SYNC_ADDR_LEN 6 +const uint8_t WELCOME_RECV_SYNC_ADDR[WELCOME_RECV_SYNC_ADDR_LEN] = + {0x55, 0x55, 0x55, 0x36, 0x36, 0x36}; + // Encoded answer choice is sent as follows: /* @@ -111,6 +149,8 @@ class iClickerEmulator bool begin(iClickerChannel chan); bool submitAnswer(uint8_t id[ICLICKER_ID_LEN], iClickerAnswer ans, bool withAck=false, uint32_t timeout=DEFAULT_ACK_TIMEOUT, bool waitClear = true); + void sendWelcomePacket(char* msg, QuestionMode mode=QuestionModes::MULTIPLE_CHOICE, + uint16_t num_questions=0); void acknowledgeAnswer(iClickerAnswerPacket* packet, bool accept=true); void startPromiscuous(iClickerChannelType chanType, void (*cb)(iClickerPacket *)); diff --git a/emulator/iSkipper/iClickerRadio.cpp b/emulator/iSkipper/iClickerRadio.cpp index 12e79d4..bb20965 100644 --- a/emulator/iSkipper/iClickerRadio.cpp +++ b/emulator/iSkipper/iClickerRadio.cpp @@ -61,7 +61,7 @@ bool iClickerRadio::initialize(uint8_t freqBand) /* 0x37 */ { REG_PACKETCONFIG1, RF_PACKET1_FORMAT_FIXED | RF_PACKET1_DCFREE_OFF | RF_PACKET1_CRC_OFF | RF_PACKET1_CRCAUTOCLEAR_ON | RF_PACKET1_ADRSFILTERING_OFF }, /* 0x38 */ { REG_PAYLOADLENGTH, PAYLOAD_LENGTH_SEND }, // in variable length mode: the max frame size, not used in TX ///* 0x39 */ { REG_NODEADRS, nodeID }, // turned off because we're not using address filtering - /* 0x3C */ { REG_FIFOTHRESH, RF_FIFOTHRESH_TXSTART_FIFOTHRESH | RF_FIFOTHRESH_TXSTART_FIFOTHRESH_IC }, // TX on FIFO not empty + /* 0x3C */ { REG_FIFOTHRESH, RF_FIFOTHRESH_TXSTART_FIFONOTEMPTY | RF_FIFOTHRESH_VALUE }, // TX on FIFO not empty /* 0x3D */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_1BIT | RF_PACKET2_AUTORXRESTART_OFF | RF_PACKET2_AES_OFF }, // RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent) //for BR-19200: /* 0x3D */ { REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_NONE | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF }, // RXRESTARTDELAY must match transmitter PA ramp-down time (bitrate dependent) /* 0x6F */ { REG_TESTDAGC, RF_DAGC_IMPROVED_LOWBETA0 }, // run DAGC continuously in RX mode for Fading Margin Improvement, recommended default for AfcLowBetaOn=0 @@ -126,8 +126,20 @@ void iClickerRadio::setChannelType(iClickerChannelType_t chanType) // dont change it if allready correct if (_chanType != chanType) { _chanType = chanType; - setFrequency(_chanType == CHANNEL_SEND ? _chan.send : _chan.recv); - setPayloadLength(_chanType == CHANNEL_SEND ? PAYLOAD_LENGTH_SEND : PAYLOAD_LENGTH_RECV, false); + switch (chanType) { + case CHANNEL_SEND: + setFrequency(_chan.send); + setPayloadLength(PAYLOAD_LENGTH_SEND, false); + break; + case CHANNEL_RECV: + setFrequency(_chan.recv); + setPayloadLength(PAYLOAD_LENGTH_RECV, false); + break; + case CHANNEL_RECV_WELCOME: + setFrequency(_chan.recv); + setPayloadLength(WELCOME_PACKET_SIZE, false); + break; + } } } diff --git a/emulator/iSkipper/iClickerRadio.h b/emulator/iSkipper/iClickerRadio.h index f5b7dec..ef54394 100644 --- a/emulator/iSkipper/iClickerRadio.h +++ b/emulator/iSkipper/iClickerRadio.h @@ -5,9 +5,6 @@ #include "iClickerRadioIncludes.h" #include "RFM69.h" -// threshold for triggerins fifo transmit interrupt -#define RF_FIFOTHRESH_TXSTART_FIFOTHRESH_IC 0x04 - //RegSyncValue1-8 for sending is: // 0x85, 0x85, 0x85, 0, 0... diff --git a/emulator/iSkipper/iClickerRadioIncludes.h b/emulator/iSkipper/iClickerRadioIncludes.h index eecbfcc..1d5fb63 100644 --- a/emulator/iSkipper/iClickerRadioIncludes.h +++ b/emulator/iSkipper/iClickerRadioIncludes.h @@ -19,11 +19,13 @@ //packet length #define PAYLOAD_LENGTH_SEND 0x05 #define PAYLOAD_LENGTH_RECV 0x05 +#define WELCOME_PACKET_SIZE 13 typedef enum iClickerChannelType { CHANNEL_SEND = 0, //act as a clicker CHANNEL_RECV = 1, // act as base station + CHANNEL_RECV_WELCOME = 2 // act as base station sending a welcome message } iClickerChannelType_t; /*