root / EtherCard / examples / etherNode / etherNode.pde
History | View | Annotate | Download (9.2 KB)
| 1 | 7763 | jcw | //>>> The latest version of this code can be found at https://github.com/jcw/ !! |
|---|---|---|---|
| 2 | 7763 | jcw | |
| 3 | 7719 | jcw | // Arduino demo sketch for testing RFM12B + ethernet |
| 4 | 5692 | jcw | // Listens for RF12 messages and displays valid messages on a webpage |
| 5 | 5692 | jcw | // Memory usage exceeds 1K, so use Atmega328 or decrease history/buffers |
| 6 | 5692 | jcw | // |
| 7 | 5692 | jcw | // This sketch is derived from RF12eth.pde: |
| 8 | 5692 | jcw | // May 2010, Andras Tucsni, http://opensource.org/licenses/mit-license.php |
| 9 | 5692 | jcw | // |
| 10 | 5692 | jcw | // The EtherCard library is based on Guido Socher's driver, licensed as GPL2. |
| 11 | 5692 | jcw | // |
| 12 | 5692 | jcw | // Mods bij jcw, 2010-05-20 |
| 13 | 5692 | jcw | |
| 14 | 5692 | jcw | #include <EtherCard.h> |
| 15 | 5692 | jcw | #include <Ports.h> |
| 16 | 5692 | jcw | #include <RF12.h> |
| 17 | 5882 | jcw | #include <avr/eeprom.h> |
| 18 | 5692 | jcw | |
| 19 | 7720 | jcw | #define DEBUG 1 // set to 1 to display free RAM on web page |
| 20 | 7720 | jcw | #define SERIAL 0 // set to 1 to show incoming requests on serial port |
| 21 | 5882 | jcw | |
| 22 | 5882 | jcw | #define CONFIG_EEPROM_ADDR ((byte*) 0x10) |
| 23 | 5882 | jcw | |
| 24 | 5882 | jcw | // configuration, as stored in EEPROM |
| 25 | 5882 | jcw | struct Config {
|
| 26 | 5882 | jcw | byte band; |
| 27 | 5882 | jcw | byte group; |
| 28 | 5882 | jcw | byte collect; |
| 29 | 5882 | jcw | word refresh; |
| 30 | 5882 | jcw | byte valid; // keep this as last byte |
| 31 | 5882 | jcw | } config; |
| 32 | 5882 | jcw | |
| 33 | 5882 | jcw | // ethernet interface mac address - must be unique on your network |
| 34 | 7724 | jcw | static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
|
| 35 | 5692 | jcw | |
| 36 | 5882 | jcw | // buffer for an outgoing data packet |
| 37 | 5882 | jcw | static byte outBuf[RF12_MAXDATA], outDest; |
| 38 | 5882 | jcw | static char outCount = -1; |
| 39 | 5882 | jcw | |
| 40 | 5692 | jcw | #define NUM_MESSAGES 10 // Number of messages saved in history |
| 41 | 5882 | jcw | #define MESSAGE_TRUNC 15 // Truncate message payload to reduce memory use |
| 42 | 5692 | jcw | |
| 43 | 6260 | jcw | static BufferFiller bfill; // used as cursor while filling the buffer |
| 44 | 5692 | jcw | |
| 45 | 5692 | jcw | static byte history_rcvd[NUM_MESSAGES][MESSAGE_TRUNC+1]; //history record |
| 46 | 5692 | jcw | static byte history_len[NUM_MESSAGES]; // # of RF12 messages+header in history |
| 47 | 5882 | jcw | static byte next_msg; // pointer to next rf12rcvd line |
| 48 | 5882 | jcw | static word msgs_rcvd; // total number of lines received modulo 10,000 |
| 49 | 5692 | jcw | |
| 50 | 7724 | jcw | byte Ethernet::buffer[1000]; // tcp/ip send and receive buffer |
| 51 | 5692 | jcw | |
| 52 | 5882 | jcw | static void loadConfig() {
|
| 53 | 5882 | jcw | for (byte i = 0; i < sizeof config; ++i) |
| 54 | 5882 | jcw | ((byte*) &config)[i] = eeprom_read_byte(CONFIG_EEPROM_ADDR + i); |
| 55 | 5882 | jcw | if (config.valid != 253) {
|
| 56 | 5882 | jcw | config.valid = 253; |
| 57 | 5882 | jcw | config.band = 8; |
| 58 | 5882 | jcw | config.group = 1; |
| 59 | 5882 | jcw | config.collect = 1; |
| 60 | 5882 | jcw | config.refresh = 5; |
| 61 | 5882 | jcw | } |
| 62 | 5882 | jcw | byte freq = config.band == 4 ? RF12_433MHZ : |
| 63 | 5882 | jcw | config.band == 8 ? RF12_868MHZ : |
| 64 | 5882 | jcw | RF12_915MHZ; |
| 65 | 7719 | jcw | rf12_initialize(31, freq, config.group); |
| 66 | 5882 | jcw | } |
| 67 | 5882 | jcw | |
| 68 | 5882 | jcw | static void saveConfig() {
|
| 69 | 5882 | jcw | for (byte i = 0; i < sizeof config; ++i) |
| 70 | 5882 | jcw | eeprom_write_byte(CONFIG_EEPROM_ADDR + i, ((byte*) &config)[i]); |
| 71 | 5882 | jcw | } |
| 72 | 5882 | jcw | |
| 73 | 7720 | jcw | #if DEBUG |
| 74 | 7720 | jcw | static int freeRam () {
|
| 75 | 7720 | jcw | extern int __heap_start, *__brkval; |
| 76 | 7720 | jcw | int v; |
| 77 | 7720 | jcw | return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); |
| 78 | 7720 | jcw | } |
| 79 | 7720 | jcw | #endif |
| 80 | 7720 | jcw | |
| 81 | 5692 | jcw | void setup(){
|
| 82 | 7720 | jcw | #if SERIAL |
| 83 | 5882 | jcw | Serial.begin(57600); |
| 84 | 5882 | jcw | Serial.println("\n[etherNode]");
|
| 85 | 7720 | jcw | #endif |
| 86 | 5882 | jcw | loadConfig(); |
| 87 | 7714 | jcw | |
| 88 | 7725 | jcw | if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) |
| 89 | 7725 | jcw | Serial.println( "Failed to access Ethernet controller"); |
| 90 | 7725 | jcw | if (!ether.dhcpSetup()) |
| 91 | 7725 | jcw | Serial.println("DHCP failed");
|
| 92 | 7720 | jcw | #if SERIAL |
| 93 | 7725 | jcw | ether.printIp("IP: ", ether.myip);
|
| 94 | 7720 | jcw | #endif |
| 95 | 5692 | jcw | } |
| 96 | 5692 | jcw | |
| 97 | 5695 | jcw | char okHeader[] PROGMEM = |
| 98 | 5695 | jcw | "HTTP/1.0 200 OK\r\n" |
| 99 | 5695 | jcw | "Content-Type: text/html\r\n" |
| 100 | 5695 | jcw | "Pragma: no-cache\r\n" |
| 101 | 5695 | jcw | ; |
| 102 | 5695 | jcw | |
| 103 | 5692 | jcw | static void homePage(BufferFiller& buf) {
|
| 104 | 5882 | jcw | word mhz = config.band == 4 ? 433 : config.band == 8 ? 868 : 915; |
| 105 | 5752 | jcw | buf.emit_p(PSTR("$F\r\n"
|
| 106 | 5695 | jcw | "<meta http-equiv='refresh' content='$D'/>" |
| 107 | 5695 | jcw | "<title>RF12 etherNode - $D MHz, group $D</title>" |
| 108 | 5882 | jcw | "RF12 etherNode - $D MHz, group $D " |
| 109 | 5882 | jcw | "- <a href='c'>configure</a> - <a href='s'>send packet</a>" |
| 110 | 5695 | jcw | "<h3>Last $D messages:</h3>" |
| 111 | 5882 | jcw | "<pre>"), okHeader, config.refresh, mhz, config.group, |
| 112 | 5882 | jcw | mhz, config.group, NUM_MESSAGES); |
| 113 | 5692 | jcw | for (byte i = 0; i < NUM_MESSAGES; ++i) {
|
| 114 | 5692 | jcw | byte j = (next_msg + i) % NUM_MESSAGES; |
| 115 | 5692 | jcw | if (history_len[j] > 0) {
|
| 116 | 5882 | jcw | word n = msgs_rcvd - NUM_MESSAGES + i; |
| 117 | 5882 | jcw | buf.emit_p(PSTR("\n$D$D$D$D: OK"), // hack, to show leading zero's
|
| 118 | 5882 | jcw | n/1000, (n/100) % 10, (n/10) % 10, n % 10); |
| 119 | 5692 | jcw | for (byte k = 0; k < history_len[j]; ++k) |
| 120 | 5695 | jcw | buf.emit_p(PSTR(" $D"), history_rcvd[j][k]);
|
| 121 | 5692 | jcw | } |
| 122 | 5692 | jcw | } |
| 123 | 5752 | jcw | long t = millis() / 1000; |
| 124 | 5752 | jcw | word h = t / 3600; |
| 125 | 5752 | jcw | byte m = (t / 60) % 60; |
| 126 | 5752 | jcw | byte s = t % 60; |
| 127 | 5752 | jcw | buf.emit_p(PSTR( |
| 128 | 5752 | jcw | "</pre>" |
| 129 | 5752 | jcw | "Uptime is $D$D:$D$D:$D$D"), h/10, h%10, m/10, m%10, s/10, s%10); |
| 130 | 7720 | jcw | #if DEBUG |
| 131 | 7720 | jcw | buf.emit_p(PSTR(" ($D bytes free)"), freeRam());
|
| 132 | 7720 | jcw | #endif |
| 133 | 5692 | jcw | } |
| 134 | 5692 | jcw | |
| 135 | 5692 | jcw | static int getIntArg(const char* data, const char* key, int value =-1) {
|
| 136 | 5692 | jcw | char temp[10]; |
| 137 | 7724 | jcw | if (ether.findKeyVal(data + 7, temp, sizeof temp, key) > 0) |
| 138 | 5692 | jcw | value = atoi(temp); |
| 139 | 5692 | jcw | return value; |
| 140 | 5692 | jcw | } |
| 141 | 5692 | jcw | |
| 142 | 5692 | jcw | static void configPage(const char* data, BufferFiller& buf) {
|
| 143 | 5692 | jcw | // pick up submitted data, if present |
| 144 | 5692 | jcw | if (data[6] == '?') {
|
| 145 | 5692 | jcw | byte b = getIntArg(data, "b"); |
| 146 | 5692 | jcw | byte g = getIntArg(data, "g"); |
| 147 | 5882 | jcw | byte c = getIntArg(data, "c", 0); |
| 148 | 5692 | jcw | word r = getIntArg(data, "r"); |
| 149 | 5692 | jcw | if (1 <= g && g <= 250 && 1 <= r && r <= 3600) {
|
| 150 | 5882 | jcw | // store values as new settings |
| 151 | 5882 | jcw | config.band = b; |
| 152 | 5882 | jcw | config.group = g; |
| 153 | 5882 | jcw | config.collect = c; |
| 154 | 5882 | jcw | config.refresh = r; |
| 155 | 5882 | jcw | saveConfig(); |
| 156 | 5882 | jcw | // re-init RF12 driver |
| 157 | 5882 | jcw | loadConfig(); |
| 158 | 5692 | jcw | // clear history |
| 159 | 5692 | jcw | memset(history_len, 0, sizeof history_len); |
| 160 | 5692 | jcw | // redirect to the home page |
| 161 | 5692 | jcw | buf.emit_p(PSTR( |
| 162 | 5692 | jcw | "HTTP/1.0 302 found\r\n" |
| 163 | 5692 | jcw | "Location: /\r\n" |
| 164 | 5692 | jcw | "\r\n")); |
| 165 | 5692 | jcw | return; |
| 166 | 5692 | jcw | } |
| 167 | 5692 | jcw | } |
| 168 | 5692 | jcw | // else show a configuration form |
| 169 | 5752 | jcw | buf.emit_p(PSTR("$F\r\n"
|
| 170 | 5692 | jcw | "<h3>Server node configuration</h3>" |
| 171 | 5692 | jcw | "<form>" |
| 172 | 5692 | jcw | "<p>" |
| 173 | 5695 | jcw | "Freq band <input type=text name=b value='$D' size=1> (4, 8, or 9)<br>" |
| 174 | 5695 | jcw | "Net group <input type=text name=g value='$D' size=3> (1..250)<br>" |
| 175 | 5882 | jcw | "Collect mode: <input type=checkbox name=c value='1' $S> " |
| 176 | 5882 | jcw | "Don't send ACKs<br><br>" |
| 177 | 5695 | jcw | "Refresh rate <input type=text name=r value='$D' size=4> (1..3600 seconds)" |
| 178 | 5692 | jcw | "</p>" |
| 179 | 5692 | jcw | "<input type=submit value=Set>" |
| 180 | 5882 | jcw | "</form>"), okHeader, config.band, config.group, |
| 181 | 5882 | jcw | config.collect ? "CHECKED" : "", |
| 182 | 5882 | jcw | config.refresh); |
| 183 | 5692 | jcw | } |
| 184 | 5692 | jcw | |
| 185 | 5882 | jcw | static void sendPage(const char* data, BufferFiller& buf) {
|
| 186 | 5882 | jcw | // pick up submitted data, if present |
| 187 | 5882 | jcw | const char* p = strstr(data, "b="); |
| 188 | 5882 | jcw | byte d = getIntArg(data, "d"); |
| 189 | 5882 | jcw | if (data[6] == '?' && p != 0 && 0 <= d && d <= 31) {
|
| 190 | 5882 | jcw | // prepare to send data as soon as possible in loop() |
| 191 | 5883 | jcw | outDest = d & RF12_HDR_MASK ? RF12_HDR_DST | d : 0; |
| 192 | 5882 | jcw | outCount = 0; |
| 193 | 5882 | jcw | // convert the input string to a number of decimal data bytes in outBuf |
| 194 | 5882 | jcw | ++p; |
| 195 | 5882 | jcw | while (*p != 0 && *p != '&') {
|
| 196 | 5882 | jcw | outBuf[outCount] = 0; |
| 197 | 5882 | jcw | while ('0' <= *++p && *p <= '9')
|
| 198 | 5882 | jcw | outBuf[outCount] = 10 * outBuf[outCount] + (*p - '0'); |
| 199 | 5882 | jcw | ++outCount; |
| 200 | 5882 | jcw | } |
| 201 | 7720 | jcw | #if SERIAL |
| 202 | 5882 | jcw | Serial.print("Send to ");
|
| 203 | 5882 | jcw | Serial.print(outDest, DEC); |
| 204 | 5882 | jcw | Serial.print(':');
|
| 205 | 5882 | jcw | for (byte i = 0; i < outCount; ++i) {
|
| 206 | 5882 | jcw | Serial.print(' ');
|
| 207 | 5882 | jcw | Serial.print(outBuf[i], DEC); |
| 208 | 5882 | jcw | } |
| 209 | 5882 | jcw | Serial.println(); |
| 210 | 5882 | jcw | #endif |
| 211 | 5882 | jcw | // redirect to home page |
| 212 | 5882 | jcw | buf.emit_p(PSTR( |
| 213 | 5882 | jcw | "HTTP/1.0 302 found\r\n" |
| 214 | 5882 | jcw | "Location: /\r\n" |
| 215 | 5882 | jcw | "\r\n")); |
| 216 | 5882 | jcw | return; |
| 217 | 5882 | jcw | } |
| 218 | 5882 | jcw | // else show a send form |
| 219 | 5882 | jcw | buf.emit_p(PSTR("$F\r\n"
|
| 220 | 5882 | jcw | "<h3>Send a wireless data packet</h3>" |
| 221 | 5882 | jcw | "<form>" |
| 222 | 5882 | jcw | "<p>" |
| 223 | 5882 | jcw | "Data bytes <input type=text name=b size=50> (decimal)<br>" |
| 224 | 5882 | jcw | "Destination node <input type=text name=d size=3> " |
| 225 | 5882 | jcw | "(1..31, or 0 to broadcast)<br>" |
| 226 | 5882 | jcw | "</p>" |
| 227 | 5882 | jcw | "<input type=submit value=Send>" |
| 228 | 5882 | jcw | "</form>"), okHeader); |
| 229 | 5882 | jcw | } |
| 230 | 5882 | jcw | |
| 231 | 5692 | jcw | void loop(){
|
| 232 | 7724 | jcw | word len = ether.packetReceive(); |
| 233 | 7724 | jcw | word pos = ether.packetLoop(len); |
| 234 | 5692 | jcw | // check if valid tcp data is received |
| 235 | 5692 | jcw | if (pos) {
|
| 236 | 7724 | jcw | bfill = ether.tcpOffset(); |
| 237 | 7724 | jcw | char* data = (char *) Ethernet::buffer + pos; |
| 238 | 7720 | jcw | #if SERIAL |
| 239 | 5882 | jcw | Serial.println(data); |
| 240 | 5882 | jcw | #endif |
| 241 | 5692 | jcw | // receive buf hasn't been clobbered by reply yet |
| 242 | 5692 | jcw | if (strncmp("GET / ", data, 6) == 0)
|
| 243 | 5692 | jcw | homePage(bfill); |
| 244 | 5692 | jcw | else if (strncmp("GET /c", data, 6) == 0)
|
| 245 | 5692 | jcw | configPage(data, bfill); |
| 246 | 5882 | jcw | else if (strncmp("GET /s", data, 6) == 0)
|
| 247 | 5882 | jcw | sendPage(data, bfill); |
| 248 | 5692 | jcw | else |
| 249 | 5692 | jcw | bfill.emit_p(PSTR( |
| 250 | 5692 | jcw | "HTTP/1.0 401 Unauthorized\r\n" |
| 251 | 5692 | jcw | "Content-Type: text/html\r\n" |
| 252 | 5692 | jcw | "\r\n" |
| 253 | 5692 | jcw | "<h1>401 Unauthorized</h1>")); |
| 254 | 7724 | jcw | ether.httpServerReply(bfill.position()); // send web page data |
| 255 | 5692 | jcw | } |
| 256 | 5692 | jcw | |
| 257 | 5692 | jcw | // RFM12 loop runner, don't report acks |
| 258 | 5692 | jcw | if (rf12_recvDone() && rf12_crc == 0 && (rf12_hdr & RF12_HDR_CTL) == 0) {
|
| 259 | 5692 | jcw | history_rcvd[next_msg][0] = rf12_hdr; |
| 260 | 5692 | jcw | for (byte i = 0; i < rf12_len; ++i) |
| 261 | 5692 | jcw | if (i < MESSAGE_TRUNC) |
| 262 | 5692 | jcw | history_rcvd[next_msg][i+1] = rf12_data[i]; |
| 263 | 5692 | jcw | history_len[next_msg] = rf12_len < MESSAGE_TRUNC ? rf12_len+1 |
| 264 | 5692 | jcw | : MESSAGE_TRUNC+1; |
| 265 | 5692 | jcw | next_msg = (next_msg + 1) % NUM_MESSAGES; |
| 266 | 5882 | jcw | msgs_rcvd = (msgs_rcvd + 1) % 10000; |
| 267 | 5882 | jcw | |
| 268 | 6540 | jcw | if (RF12_WANTS_ACK && !config.collect) {
|
| 269 | 7720 | jcw | #if SERIAL |
| 270 | 5882 | jcw | Serial.println(" -> ack");
|
| 271 | 7720 | jcw | #endif |
| 272 | 6540 | jcw | rf12_sendStart(RF12_ACK_REPLY, 0, 0); |
| 273 | 5882 | jcw | } |
| 274 | 5692 | jcw | } |
| 275 | 5882 | jcw | |
| 276 | 5882 | jcw | // send a data packet out if requested |
| 277 | 5882 | jcw | if (outCount >= 0 && rf12_canSend()) {
|
| 278 | 5882 | jcw | rf12_sendStart(outDest, outBuf, outCount, 1); |
| 279 | 5882 | jcw | outCount = -1; |
| 280 | 5882 | jcw | } |
| 281 | 5692 | jcw | } |