/* Sense/Net Bus Command-Line Utility for Raspberry Pi. * Sensiplicity Systems * * Copyright 2015 * * Department of Botany and Plant Pathology * Center for Genome Research and Biocomputing * Oregon State University * Corvallis, OR 97331 * * This program is not free software; you can not redistribute it and/or * modify it at all. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include "sense-net.h" #include "soil-sensor.h" #include #include #include #include #include const char *Usage = "sn-util performs operations on a Sense/Net bus.\n" "Usage:\n" " sn-util-rpi enum \n" " Enumerate all nodes on the Sense/Net bus. Output is a CSV file to stdout:\n" " Node, Bus, Typecode, Type_Description\n" " Bus # is the number of the bus (1, 2, or 3) to enumerate.\n" " sn-util-rpi get [ ...] \n" " Get data from a node or nodes. If successful, lines of CSV-formatted data will\n" " be sent to stdout: a column header and a row of data for each node.\n" " Node is the address of the node from which to retrieve data.\n" " If \"all\" is used as a node ID, the bus will be enumerated and data will\n" " be retrieved from all nodes found.\n" " Bus # is the number of the bus (1, 2, or 3) to use.\n" " sn-util-rpi blink \n" " Blink the status LED on the given node. If \"all\" is used as a node id,\n" " the bus will be enumerated and every light will blink.\n" " sn-util-rpi upgrade \n" " Install new firmware on a sense-net node.\n" " Node is the address of the node to upgrade.\n" " If \"all\" is used as a node ID, the bus will be enumerated\n" " and all nodes will be upgraded.\n" " Bus # is the number of the bus (1, 2, or 3) to use.\n" " The firmware file must be a raw binary image containing only the\n" " main program region of the flash.\n" " sn-util-rpi send [data]\n" " Send a packet to a node.\n" " Bus # is the number of the bus (1, 2, or 3) to use.\n" " Command is an integer from 0-7, inclusive.\n" " Node is the destination address of the packet, 9 bytes, expressed in hexadecimal.\n" " Data is the data to send in the packet, 0-128 bytes (inclusive), expressed in hexadecimal.\n" " Data may be omitted.\n" " The reply, if any, will be printed to stdout as a CSV line:\n" " Code (REPLY | NO_REPLY | BUS_ERROR), Node, Status Code, Reply Data\n" " On no reply or bus error status code and reply data are empty.\n" " sn-util-rpi --help\n" " Print this message.\n" " sn-util-rpi --version\n" " Print version information.\n"; const char *Version = "sn-util (Raspberry Pi) version 1.\n" "Copyright 2015 Oregon State University.\n" "Trademark Sensisplicity Systems.\n" "This program uses WiringPi (http://wiringpi.com/).\n" "WiringPi is licensed under the GNU LGPLv3.\n" "Written by Nick Ames .\n"; /* Print an error message to stderr, appended with "sn-util-rpi: error: ..." * and ending with a newline. Arguments work the same way as printf. */ void error(const char *format, ...){ va_list args; fprintf(stderr, "sn-util-rpi: error: "); va_start(args, format); vfprintf(stderr, format, args); fprintf(stderr, "\n"); va_end(args); } /* Compare two strings in a case-insensitive way. * Returns -1 if they are different, or 0 if they are the same. */ int stricmp(const char *a, const char *b){ while(*a != 0 && *b != 0){ if(tolower(*a) != tolower(*b))return -1; a++; b++; } if(*a != 0 || *b != 0){ return -1; } else { return 0; } } /* Convert a hexadecimal string into a memory block. * ptr is set to point to a new block of memory containing the constant. * It should be free'd when no longer needed. On error, ptr is set to NULL. * On success, the size of the block is returned. On error, -1 is returned. * Specific error messages are printed to stderr. */ int mem_hex(char **ptr, const char *str){ if(ptr == NULL){ return -1; } if(str == NULL){ *ptr = NULL; return 0; } /* Check for invalid characters. Valid characters are * 0-9, A-F, X (letters lower or upper case), comma, space, and tab. */ int l; for(l = 0; str[l] != '\0'; l++){ if(!isxdigit(str[l]) && tolower(str[l]) != 'x' && str[l] != ' ' && str[l] != ',' && str[l] != '\t'){ error("invalid character %c in hexadecimal constant %s.", str[l], str); return -1; } } /* At this point, l is set to the number of characters in the string. */ *ptr = calloc(l/2 + 1, 1); if(NULL == *ptr){ error("malloc failure."); return -1; } /* Load data into the memory block. */ int si,mi; for(si = 0, mi=0; si < l; si++){ switch(str[si]){ case ',': /* Fall-through */ case ' ': /* Fall-through */ case '\n': /* Fall-through */ case '\t': break; case 'x': /* Fall-through */ case 'X': /* When interpreting an x-prefixed block, an implied 0 nybble must * be inserted when there are an odd number of characters in the block. */ /* Empty statement to avoid C technicalities: */; int b, bi; for(bi=(si+1),b=0; bi < l && isxdigit(str[bi]); bi++,b++){ /* Count the number of characters in this 0x-prefixed block. */ } if(b % 2)mi++; break; default: if('0' == str[si]){ if(si != l-1 && 'x' == tolower(str[si+1]))continue; } char tempstr[2]; tempstr[1] = '\0'; if(mi % 2){ tempstr[0] = str[si]; (*ptr)[mi/2] |= strtol(tempstr, NULL, 16); mi++; } else { tempstr[0] = str[si]; (*ptr)[mi/2] |= strtol(tempstr, NULL, 16) << 4; mi++; } } } if(mi % 2){ error("odd number of nybbles in hexadecimal constant %s", str); free(*ptr); *ptr = NULL; return -1; } else { return mi/2; } } /* Print a block of memory to stdout as a hexadecimal constant, prefixed with "0x". */ void print_hex(void *data, int size){ if(0 == size)return; uint8_t *udata = (uint8_t *) data; printf("0x"); for(int i=0;i 3){ error("Invalid bus number %s; expected 1, 2, or 3.", argv[2]); return -1; } node_enum_t *list; int num_nodes; fflush(stderr); sn_enumerate(bus, &list, &num_nodes); if(num_nodes == 0){ return 0; } else { printf("Typecode, Type_Description, Bus, Node_ID\n"); } for(int i=0;i < num_nodes;i++){ printf("0x%04X, %s, %d, ", list[i].type, sn_type_str(list[i].type), bus); print_hex(&(list[i].id), 9); printf("\n"); } free(list); return 0; } /* Get data from the given node(s). * Returns 0 on success, -1 on error. */ int get_cmd(int argc, char *argv[]){ if(argc % 2){ error("incorrect number of arguments for get command."); fputs(Usage, stderr); return -1; } soil_print_header(stdout); for(int argn=2;argn < argc - 1; argn+=2){ int bus = strtol(argv[argn], NULL, 10); if(bus < 1 || bus > 3){ error("Invalid bus number %s; expected 1, 2, or 3.", argv[argn]); return -1; } if(0 == stricmp(argv[argn+1], "all")){ /* Enumerate and get data from all nodes. */ int num_nodes; node_enum_t *nodes; sn_enumerate(bus, &nodes, &num_nodes); for(int n=0;n < num_nodes; n++){ char buf[128]; int buf_size; for(int i=0;i<3;i++){ /* Retry several times if we get an error. */ if(sn_get_data(bus, nodes[n].id, buf, &buf_size))continue; if(0 == soil_parse_data(stdout, bus, nodes[n].id, (uint8_t *) buf, buf_size))break; } } } else { uint8_t *node; int node_size; node_size = mem_hex((char **) &node, argv[argn+1]); if(node_size != 9){ error("expected nine bytes for node address"); return -1; } char buf[128]; int buf_size; for(int i=0;i<3;i++){ /* Retry several times if we get an error. */ if(sn_get_data(bus, node, buf, &buf_size))continue; if(0 == soil_parse_data(stdout, bus, node, (uint8_t *) buf, buf_size))break; } } } return 0; } /* Send a packet to a node. * Returns 0 on success, -1 on error. */ int send_cmd(int argc, char *argv[]){ if(argc != 6 && argc != 5){ error("incorrect number of arguments for send command."); fputs(Usage, stderr); return -1; } int bus, command, node_size, data_size; char *node, *data; int r, status, reply_size; char reply_data[128]; bus = strtol(argv[2], NULL, 10); if(bus < 1 || bus > 3){ error("Invalid bus number %s; expected 1, 2, or 3.", argv[2]); return -1; } node_size = mem_hex(&node, argv[3]); if(node_size != 9){ error("expected nine bytes for node address"); return -1; } command = strtol(argv[4], NULL, 10); if(command < 0 || command > 7){ error("expected a command value between 0 and 7 (inclusive)."); return -1; } if(6 == argc){ data_size = mem_hex(&data, argv[5]); if(data_size < 0 || data_size > 128){ error("expected 0 to 128 bytes (inclusive) of packet data"); return -1; } } else { /* No data argument supplied. */ data_size = 0; data = NULL; } r = sn_send_packet(bus, command, (uint8_t *) node, data, data_size, &status, reply_data, &reply_size); if(0 == r){ printf("NO_REPLY,"); print_hex(node, 9); printf(",,\n"); } else if(1 == r){ printf("REPLY,"); print_hex(node, 9); printf(",%d,", status); print_hex(reply_data, reply_size); printf("\n"); } else { printf("BUS_ERROR,"); print_hex(node, 9); printf(",,\n"); } free(node); if(NULL != data)free(data); return 0; } /* Blink a node's status LED. */ void blink_cmd(int argc, char *argv[]){ if(argc % 2){ error("incorrect number of arguments for blink command."); fputs(Usage, stderr); return; } for(int argn=2;argn < argc - 1; argn+=2){ int bus = strtol(argv[argn], NULL, 10); if(bus < 1 || bus > 3){ error("Invalid bus number %s; expected 1, 2, or 3.", argv[argn]); return; } if(0 == stricmp(argv[argn+1], "all")){ /* Enumerate and blink all nodes. */ int num_nodes; node_enum_t *nodes; sn_enumerate(bus, &nodes, &num_nodes); for(int n=0;n < num_nodes; n++){ sn_send_packet(bus, 3, nodes[n].id, NULL, 0, NULL, NULL, NULL); } } else { uint8_t *node; int node_size; node_size = mem_hex((char **) &node, argv[argn+1]); if(node_size != 9){ error("expected nine bytes for node address"); return; } sn_send_packet(bus, 3, node, NULL, 0, NULL, NULL, NULL); } } } /* Compute and set the overall CRC of the whole main firmware flash space. * A CRC is first computed for each set of six flash pages, then an * overall CRC is computed from those. */ void calc_flash_crc(uint8_t *flash){ uint8_t sixpage_crcs[64]; /* CRCs of each set of six pages. */ uint16_t temp; for(int i=0; i<32; i++){ if(31 == i){ /* Exclude the last two bytes of the last page, * as they contain the overall CRC. */ temp = calc_crc(flash + (i * 192), 190); sixpage_crcs[i * 2] = temp & 0xFF; sixpage_crcs[(i * 2) + 1] = (temp >> 8) & 0xFF; } else { temp = calc_crc(flash + (i * 192), 192); sixpage_crcs[i * 2] = temp & 0xFF; sixpage_crcs[(i * 2) + 1] = (temp >> 8) & 0xFF; } } temp = calc_crc((uint8_t *) sixpage_crcs, 64); flash[6142] = temp & 0xFF; flash[6143] = (temp >> 8) & 0xFF; } /* Burn firmware to a node. */ int program_node(int bus, uint8_t *node, char *fwbuf, const char *fwname){ /* Calculate CRC. */ calc_flash_crc((uint8_t *) fwbuf); /* Status reporting. */ printf("Programming node "); print_hex(node, 9); printf(" with %s: ", fwname); fflush(stdout); /* Reboot */ int r; printf("Reboot"); fflush(stdout); char packetbuf[33]; int reply; r = sn_send_packet_r(bus, 6, node, NULL, 0, &reply, NULL, NULL); if(r != 1){ error("did not receive a reply."); return -1; } if(reply != 4){ error("unexpected reply code from node."); return -1; } /* Enter programming mode. */ printf(". Enter programming mode"); fflush(stdout); usleep(100000); r = sn_send_packet_r(bus, 4, node, NULL, 0, &reply, NULL, NULL); if(r != 1){ error("did not receive a reply."); return -1; } if(reply != 4){ error("unexpected reply code from node."); return -1; } /* Erase pages. */ printf(". Erasing..."); fflush(stdout); for(int page=0; page < 192; page++){ packetbuf[0] = page; r = sn_send_packet_r(bus, 7, node, packetbuf, 1, &reply, NULL, NULL); if(r != 1){ error("did not receive a reply."); return -1; } if(reply != 4){ error("unexpected reply code from node."); return -1; } usleep(5000); } /* Program pages. */ printf(" Programming... "); fflush(stdout); for(int page=0; page < 192; page++){ packetbuf[0] = page; memcpy(packetbuf + 1, fwbuf + (32 * page), 32); r = sn_send_packet_r(bus, 5, node, packetbuf, 33, &reply, NULL, NULL); if(r != 1){ error("did not receive a reply."); return -1; } if(reply != 4){ error("unexpected reply code from node."); return -1; } usleep(5000); } /* Reboot */ printf("Reboot. "); fflush(stdout); r = sn_send_packet_r(bus, 6, node, NULL, 0, &reply, NULL, NULL); if(r != 1){ error("did not receive a reply."); return -1; } if(reply != 4){ error("unexpected reply code from node."); return -1; } /* Done. */ printf("Done.\n"); return 0; } /* Burn firmware. * Returns 0 on success, -1 on error. */ int firm_cmd(int argc, char *argv[]){ if(argc != 5){ error("incorrect number of arguments for upgrade command."); fputs(Usage, stderr); return -1; } int bus, node_size; uint8_t *node; int all = 0; FILE *fwfile; bus = strtol(argv[2], NULL, 10); if(bus < 1 || bus > 3){ error("Invalid bus number %s; expected 1, 2, or 3.", argv[2]); return -1; } if(0 == stricmp(argv[3], "all")){ all = 1; } else { node_size = mem_hex((char **) (&node), argv[3]); if(node_size != 9){ error("expected nine bytes for node address"); return -1; } } fwfile = fopen(argv[4], "rb"); if(NULL == fwfile){ error("could not open firmware file %s", argv[4]); perror(NULL); return -1; } /* Check that the file isn't too large. */ fseek(fwfile, 0, SEEK_END); int fwsize = ftell(fwfile); if(fwsize > 6144){ error("firmware file %s too large", argv[4]); return -1; } /* Load firmware into memory. */ char *fwbuf = malloc(6144); if(NULL == fwbuf){ error("malloc failure."); return -1; } memset(fwbuf, 0xFF, 6144); rewind(fwfile); fread(fwbuf, 1, fwsize, fwfile); fclose(fwfile); calc_flash_crc(fwbuf); if(all){ struct node_enum_t *nodes; int num_nodes; sn_enumerate(bus, &nodes, &num_nodes); for(int i=0; i