/* Copyright (C) 2005 Boisy G. Pitre This utility bridges the LaCrosse WS-2315 Weather Station to ModBus Ethernet. For now, only ModBus code 0x03 (Read Holding Registers) is supported. Boisy G. Pitre December 27, 2005 */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #endif #include #include #include "rw2300.h" #define MSG_WAIT_TIME_MS 1000 #define DEFAULT_DEBUG_LEVEL 0 /* Function prototypes */ void fetch2300(WEATHERSTATION ws2300, struct config_type config, float *modbusregs); static void dump(u_char *buffer, size_t num_bytes, int format); unsigned debuglevel = DEFAULT_DEBUG_LEVEL; char const *progname; char base_directory[PATH_MAX]; WEATHERSTATION ws2300; struct config_type config; bool die; /* ModBus Register Mapping of WS2300 */ #define MAXREGS 125 float modbusregs[MAXREGS]; /* array of floats (byteswapped for x86 if necessary) */ char modbus_packet[256]; /* The signal handler */ void sig_handler (int sig) { die = true; } /* This function processes ModBus/TCP function code 03 (Read Registers) */ bool processModBusCode03(int newtcp_fd_) { int n_bytes, startreg, numregs; /* Obtain the 4 bytes (2 of start, 2 of count) */ n_bytes = recv(newtcp_fd_, &modbus_packet[8], 4, 0); if (n_bytes < 4) { perror("modbus opcode 3"); return false; } if (debuglevel > 5) { printf("Received:\n"); dump((u_char *)modbus_packet, 12, 0); printf("\n"); } /* Cull start register and number of registers */ startreg = modbus_packet[8] << 8 | modbus_packet[9]; numregs = modbus_packet[10] << 8 | modbus_packet[11]; if (debuglevel > 0) { printf("Start Register: %d .... Number of Registers: %d\n", startreg, numregs); } /* START MODIFYING THE PACKET TO MAKE IT A RESPONSE PACKET */ modbus_packet[8] = 4 * numregs; /* set size */ for (n_bytes = 0; n_bytes < 5; n_bytes++) { modbus_packet[n_bytes] = 0; } /* Modify length field */ modbus_packet[5] = 3 + 4 * numregs; /* Fetch WS2300 values */ fetch2300(ws2300, config, modbusregs); /* Send out response */ if (debuglevel > 5) { printf("Sending:\n"); dump((u_char *)modbus_packet, 9, 0); dump((u_char *)&modbusregs[startreg], 4*numregs, 0); printf("\n"); } send(newtcp_fd_, modbus_packet, 9, 0); send(newtcp_fd_, (char *)&modbusregs[startreg], 4*numregs, 0); return true; } int accept_modbus_connection (int modbusfd_) { int acceptfd; if ((acceptfd = accept (modbusfd_, NULL, NULL)) == -1) { perror ("accept connection"); } return acceptfd; } bool receive_modbus_command (int newtcp_fd_) { int n_bytes; /* ModBus/TCP Packet: Transaction ID (2 bytes) Protocol ID (2 bytes) Length (2 bytes) Unit ID (1 byte) ModBus Function code (1 byte) */ /* Read the 6 byte ModBus/TCP header, 1 byte address, and 1 byte function code */ if ((n_bytes = recv (newtcp_fd_, modbus_packet, 8, 0)) == -1) { perror ("tcp recv:"); return false; } if (n_bytes == 0) { return false; } /* Determine function code and next path */ switch (modbus_packet[7]) { /* Read Holding Registers */ case 3: processModBusCode03(newtcp_fd_); break; /* We ignore others for now! */ } return true; } int modbus_socket_init (unsigned modbusport_) { int modbusfd = socket (PF_INET, SOCK_STREAM, 0); if (modbusfd == -1) { perror ("modbus socket"); shutdown (modbusfd, SHUT_RDWR); close (modbusfd); return -1; } if (fcntl (modbusfd, F_SETFL, O_NONBLOCK) == -1) { perror ("fcntl"); shutdown (modbusfd, SHUT_RDWR); close (modbusfd); return -1; } struct sockaddr_in tcpaddr; tcpaddr.sin_family = AF_INET; tcpaddr.sin_addr.s_addr = INADDR_ANY; tcpaddr.sin_port = htons (modbusport_); int reuse = 1; if (setsockopt (modbusfd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse, sizeof (reuse)) == -1) { perror( "setsockopt" ); shutdown (modbusfd, SHUT_RDWR); close (modbusfd); return -1; } if (bind (modbusfd, (struct sockaddr *) &tcpaddr, sizeof (tcpaddr)) == -1) { perror ("modbus bind"); shutdown (modbusfd, SHUT_RDWR); close (modbusfd); return -1; } return modbusfd; } float floatswap(float value) { float modified; char xc, *x = (char *)&modified; modified = value; xc = x[0]; x[0] = x[1]; x[1] = xc; xc = x[2]; x[2] = x[3]; x[3] = xc; #if 0 xc = x[0]; x[0] = x[3]; x[3] = xc; xc = x[2]; x[2] = x[1]; x[1] = xc; #endif return modified; } int main (int argc, char **argv) { int opt; bool usage_error = false; struct pollfd pfd[3]; getcwd (base_directory, sizeof (base_directory)); if (debuglevel > 0) { printf("'All Your Base' Directory = %s\n", base_directory); } progname = basename(argv[0]) ; while ((opt = getopt (argc, argv, "s:m:x:?")) != -1) { switch (opt) { case 'x': if (sscanf (optarg, "%u", &debuglevel) != 1) { usage_error = true; } else { printf("Debugging on at level %u\n", debuglevel); } break; default: usage_error = true; break; } } if (usage_error) { fprintf(stderr, "Usage: %s \n" "\t[-x debuglevel]\tDebugging level [%d]\n", progname, DEFAULT_DEBUG_LEVEL); return -1; } get_configuration(&config, argv[1]); ws2300 = open_weatherstation(config.serial_device_name); pfd[0].fd = modbus_socket_init(config.modbus_port); pfd[1].fd = -1; if (pfd[0].fd == -1) { fprintf(stderr, "%s : Unable to open modbus socket\n", progname); return -3; } if ((listen (pfd[0].fd, 1)) == -1) { fprintf(stderr, "%s : Error listening on modbus socket\n", progname); return -3; } if (debuglevel > 0) { printf("listening on port %d\n", config.modbus_port); } pfd[0].events = POLLIN; pfd[1].events = POLLIN; pfd[0].revents = 0; pfd[1].revents = 0; signal (SIGINT, sig_handler); signal (SIGTERM, sig_handler); while (!die) { int status = 0; double tstamp; struct timeval tv; status = poll (pfd, 2, MSG_WAIT_TIME_MS); if (status == -1) { perror ("poll"); continue; } if (pfd[0].revents & POLLIN) { pfd[1].fd = accept_modbus_connection (pfd[0].fd); if (pfd[1].fd == -1) { fprintf(stderr, "%s : Control connection failed\n", progname); } } if (pfd[1].revents & POLLIN) { if (!receive_modbus_command (pfd[1].fd)) { shutdown (pfd[1].fd, SHUT_RDWR); close (pfd[1].fd); pfd[1].fd = -1; } } gettimeofday (&tv, 0); tstamp = tv.tv_sec + tv.tv_usec / 1000000.0; } close_weatherstation(ws2300); shutdown(pfd[0].fd, SHUT_RDWR); close(pfd[0].fd); if (pfd[1].fd != -1) { shutdown(pfd[1].fd, SHUT_RDWR); close(pfd[1].fd); } return 0; } static int byte_count; static u_int dumpchunk = 16; #define BUFFSIZ 256 static void dump_header(int format); static int displayLabel = 1; static int displayASCII = 1; static int displayHeader = 1; static void dump_line(u_char *buffer, int count, int format); static char *binary(char s); static void dump(u_char *buffer, size_t num_bytes, int format) { u_int i; byte_count = 0; for (i = 0; i < num_bytes; i += dumpchunk) { if (byte_count % BUFFSIZ == 0) { dump_header(format); } /* print line header */ if (format == 0) { if (displayLabel == 1) { printf("\n%08x ", byte_count); } else { printf("\n"); } } else { if (displayLabel == 1) { printf("\nL%04X fcb ", byte_count); } else { printf("\n fcb "); } } if (num_bytes - i > dumpchunk) { dump_line(&buffer[i], dumpchunk, format); byte_count += dumpchunk; } else { dump_line(&buffer[i], num_bytes - i, format); byte_count += num_bytes - i; } } return; } static void dump_line(u_char *buffer, int count, int format) { int i; int carry = 0; if (count % 2 != 0) { count--; carry = 1; } for (i = 0; i < count; i += 2) { switch (format) { case 0: printf("%02x%02x ", buffer[i], buffer[i + 1]); break; case 1: if (i == count - 2 && carry == 0) { printf("$%02X,$%02X", buffer[i], buffer[i + 1]); } else { printf("$%02X,$%02X,", buffer[i], buffer[i + 1]); } break; case 2: if (i == count - 2 && carry == 0) { printf("%%%s,%%%s", binary(buffer[i]), binary(buffer[i + 1])); } else { printf("%%%s,%%%s,", binary(buffer[i]), binary(buffer[i + 1])); } break; } } if (carry == 1) { switch (format) { case 0: printf("%02x", buffer[i]); break; case 1: printf("$%02X", buffer[i]); break; case 2: printf("%%%s", binary(buffer[i])); break; } count++; } if (displayASCII == 1) { /* make spaces available if last line is not full */ i = (dumpchunk - count); if (format == 1) { printf(" "); } if (i % 2 != 0) { switch (format) { case 1: printf(" "); break; default: printf(" "); break; } } i /= 2; while (i--) { if (format == 1) { printf(" "); } else { printf(" "); } } /* print character dump on right side */ for (i = 0; i < count; i++) { if (buffer[i] >= 32 && buffer[i] <= 127) { printf("%c", buffer[i]); } else if (buffer[i] >= 128+32 && buffer[i] <= 128+'z') { printf("%c", buffer[i]-128); } else { printf("."); } } } return; } static void dump_header(int format) { if (format == 0 && displayHeader == 1) { printf("\n\n Addr 0 1 2 3 4 5 6 7 8 9 A B C D E F"); if (displayASCII == 1) { printf(" 0 2 4 6 8 A C E"); } printf("\n"); printf("-------- ---- ---- ---- ---- ---- ---- ---- ----"); if (displayASCII == 1) { printf(" ----------------"); } } return; } static char *binary(char s) { static char buffer[9] = {'0', '0', '0', '0', '0', '0', '0', '0', '\0'}; int i; for (i = 0; i < 8; i++) { int x = s & (1 << (7 - i)); if (x != 0) { buffer[i] = '1'; } else { buffer[i] = '0'; } } return(buffer); } void fetch2300(WEATHERSTATION ws2300, struct config_type config, float *modbusregs) { char tendency[32], forecast[32]; char logline[3000] = ""; char datestring[50]; //used to hold the date stamp for the log file double winddir[6]; time_t basictime; double tempfloat_max; struct timestamp time_max; /* READ TEMPERATURE INDOOR */ modbusregs[0] = floatswap(temperature_indoor(ws2300, config.temperature_conv)); sprintf(logline, "%sTi %.1f\n", logline, floatswap(modbusregs[0])); /* READ TEMPERATURE OUTDOOR */ modbusregs[1] = floatswap(temperature_outdoor(ws2300, config.temperature_conv)); sprintf(logline, "%sTo %.1f\n", logline, floatswap(modbusregs[1])); /* READ DEWPOINT */ modbusregs[2] = floatswap(dewpoint(ws2300, config.temperature_conv)); sprintf(logline, "%sDP %.1f\n", logline, floatswap(modbusregs[2])); /* READ RELATIVE HUMIDITY INDOOR */ modbusregs[3] = floatswap((float)humidity_indoor(ws2300)); sprintf(logline, "%sRHi %f\n", logline, floatswap(modbusregs[3])); /* READ RELATIVE HUMIDITY OUTDOOR */ modbusregs[4] = floatswap((float)humidity_outdoor(ws2300)); sprintf(logline, "%sRHo %f\n", logline, floatswap(modbusregs[4])); /* READ WIND SPEED AND DIRECTION */ modbusregs[5] = floatswap(wind_current(ws2300, config.wind_speed_conv_factor, winddir)); sprintf(logline, "%sWS %f\n", logline, floatswap(modbusregs[5])); modbusregs[6] = floatswap(winddir[0]); sprintf(logline, "%sDIR %f\n", logline, floatswap(modbusregs[6])); /* WINDCHILL */ modbusregs[7] = floatswap(windchill(ws2300, config.temperature_conv)); sprintf(logline, "%sWC %.1f\n", logline, floatswap(modbusregs[7])); /* READ RAIN 1H */ modbusregs[8] = floatswap(rain_1h_all(ws2300, config.rain_conv_factor, &tempfloat_max, &time_max)); sprintf(logline,"%sR1h %.2f\n", logline, floatswap(modbusregs[8])); /* READ RAIN 24H */ modbusregs[9] = floatswap(rain_24h_all(ws2300, config.rain_conv_factor, &tempfloat_max, &time_max)); sprintf(logline,"%sR24h %.2f\n", logline, floatswap(modbusregs[9])); /* READ RAIN TOTAL */ modbusregs[10] = floatswap(rain_total_all(ws2300, config.rain_conv_factor, &time_max)); sprintf(logline,"%sRtot %.2f\n", logline, floatswap(modbusregs[10])); /* READ RELATIVE PRESSURE */ modbusregs[11] = floatswap(rel_pressure(ws2300, config.pressure_conv_factor)); sprintf(logline,"%sRP %.3f\n", logline, floatswap(modbusregs[11])); /* READ TENDENCY AND FORECAST */ tendency_forecast(ws2300, tendency, forecast); sprintf(logline, "%sTendency %s\nForecast %s\n", logline, tendency, forecast); if (strcmp(tendency, "Steady") == 0) modbusregs[12] = floatswap(0); if (strcmp(tendency, "Rising") == 0) modbusregs[12] = floatswap(1); if (strcmp(tendency, "Falling") == 0) modbusregs[12] = floatswap(2); if (strcmp(forecast, "Rainy") == 0) modbusregs[13] = floatswap(0); if (strcmp(forecast, "Cloudy") == 0) modbusregs[13] = floatswap(1); if (strcmp(forecast, "Sunny") == 0) modbusregs[13] = floatswap(2); /* GET DATE AND TIME FOR LOG FILE, PLACE BEFORE ALL DATA IN LOG LINE */ time(&basictime); strftime(datestring,sizeof(datestring),"Date %Y-%b-%d\nTime %H:%M:%S\n", localtime(&basictime)); // Print out and leave if (debuglevel > 0) { printf("%s%s",datestring, logline); } }