/* * Copyright © 2006 Tom Dowdy. All Rights Reserved. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static Boolean gVerbose = false; // -------------------------------------------------------------------------------------------- // Returns an iterator across all known serial ports. Caller is responsible for // releasing the iterator when iteration is complete. static kern_return_t FindSerialPorts(io_iterator_t *matchingServices) { kern_return_t kernResult; CFMutableDictionaryRef classesToMatch; // Serial devices are instances of class IOSerialBSDClient classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); if (classesToMatch == NULL) { kernResult = KERN_FAILURE; } else { CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type)); } kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices); exit: return kernResult; } // -------------------------------------------------------------------------------------------- // Given an iterator across a set of serial ports, return the BSD path to the first one. // If no serial ports are found the path name is set to an empty string. static kern_return_t GetSerialPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize, char *matchString) { io_object_t serialService; kern_return_t kernResult = KERN_FAILURE; Boolean serialFound = false; // Initialize the returned path *bsdPath = '\0'; // Iterate across all serial ports found. We will match the resulting device against the input string. while ((serialService = IOIteratorNext(serialPortIterator)) && !serialFound) { CFTypeRef bsdPathAsCFString; // Get the callout device's path (/dev/cu.xxxxx). The callout device should almost always be // used: the dialin device (/dev/tty.xxxxx) would be used when monitoring a serial port for // incoming calls, e.g. a fax listener. bsdPathAsCFString = IORegistryEntryCreateCFProperty(serialService, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); if (bsdPathAsCFString) { Boolean result; // Convert the path from a CFString to a C (NUL-terminated) string for use // with the POSIX open() call. result = CFStringGetCString(bsdPathAsCFString, bsdPath, maxPathSize, kCFStringEncodingUTF8); CFRelease(bsdPathAsCFString); if ( (result) && ((matchString[0] == 0) || (strstr(bsdPath, matchString) != nil)) ) { if (gVerbose) printf("Serial port found with BSD path: %s", bsdPath); serialFound = true; kernResult = KERN_SUCCESS; } } if (gVerbose) printf("\n"); // Release the io_service_t now that we are done with it. (void) IOObjectRelease(serialService); } if ((kernResult != KERN_SUCCESS) && (gVerbose)) printf("Failed to find serial port\n"); return kernResult; } // -------------------------------------------------------------------------------------------- // Hold the original termios attributes so we can reset them static struct termios gOriginalTTYAttrs; // Given the path to a serial device, open the device and configure it. // Return the file descriptor associated with the device. static int OpenSerialPort(const char *bsdPath) { int fileDescriptor = -1; struct termios options; // Open the serial port read/write, with no controlling terminal, and don't wait for a connection. // The O_NONBLOCK flag also causes subsequent I/O on the device to be non-blocking. // See open(2) ("man 2 open") for details. fileDescriptor = open(bsdPath, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fileDescriptor == -1) { if (gVerbose) printf("Error opening serial port %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } // Note that open() follows POSIX semantics: multiple open() calls to the same file will succeed // unless the TIOCEXCL ioctl is issued. This will prevent additional opens except by root-owned // processes. // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. if (ioctl(fileDescriptor, TIOCEXCL) == -1) { if (gVerbose) printf("Error setting TIOCEXCL on %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } // Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O will block. // See fcntl(2) ("man 2 fcntl") for details. // We don't do this so that our reads won't block after this point #if 0 if (fcntl(fileDescriptor, F_SETFL, 0) == -1) { if (gVerbose) printf("Error clearing O_NONBLOCK %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } #endif // Get the current options and save them so we can restore the default settings later. if (tcgetattr(fileDescriptor, &gOriginalTTYAttrs) == -1) { if (gVerbose) printf("Error getting tty attributes %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } // The serial port attributes such as timeouts and baud rate are set by modifying the termios // structure and then calling tcsetattr() to cause the changes to take effect. Note that the // changes will not become effective without the tcsetattr() call. // See tcsetattr(4) ("man 4 tcsetattr") for details. options = gOriginalTTYAttrs; // Print the current input and output baud rates. // See tcsetattr(4) ("man 4 tcsetattr") for details. if (gVerbose) printf("Current input baud rate is %d\n", (int) cfgetispeed(&options)); if (gVerbose) printf("Current output baud rate is %d\n", (int) cfgetospeed(&options)); // Set raw input (non-canonical) mode, with reads blocking until either a single character // has been received or a one second timeout expires. // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") for details. #if 0 cfmakeraw(&options); options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 10; #endif // The baud rate, word length, and handshake options can be set as follows: cfsetspeed(&options, B300); // Set 300 baud options.c_cflag |= (CS8 // Use 8 bit words // | PARENB // Parity enable (even parity if PARODD not also set) | CDTR_IFLOW // DTR input flow control // | CCTS_OFLOW // CTS flow control of output // | CRTS_IFLOW // RTS flow control of input ); // Print the new input and output baud rates. Note that the IOSSIOSPEED ioctl interacts with the serial driver // directly bypassing the termios struct. This means that the following two calls will not be able to read // the current baud rate if the IOSSIOSPEED ioctl was used but will instead return the speed set by the last call // to cfsetspeed. if (gVerbose) printf("Input baud rate changed to %d\n", (int) cfgetispeed(&options)); if (gVerbose) printf("Output baud rate changed to %d\n", (int) cfgetospeed(&options)); // Cause the new options to take effect immediately. if (tcsetattr(fileDescriptor, TCSANOW, &options) == -1) { if (gVerbose) printf("Error setting tty attributes %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } unsigned long mics = 1UL; // Set the receive latency in microseconds. Serial drivers use this value to determine how often to // dequeue characters received by the hardware. Most applications don't need to set this value: if an // app reads lines of characters, the app can't do anything until the line termination character has been // received anyway. The most common applications which are sensitive to read latency are MIDI and IrDA // applications. if (ioctl(fileDescriptor, IOSSDATALAT, &mics) == -1) { // set latency to 1 microsecond if (gVerbose) printf("Error setting read latency %s - %s(%d).\n", bsdPath, strerror(errno), errno); goto error; } // Success return fileDescriptor; // Failure path error: if (fileDescriptor != -1) { close(fileDescriptor); } return -1; } // Given the file descriptor for a serial device, close that device. static void CloseSerialPort(int fileDescriptor) { // Block until all written output has been sent from the device. // Note that this call is simply passed on to the serial device driver. // See tcsendbreak(3) ("man 3 tcsendbreak") for details. if (tcdrain(fileDescriptor) == -1) { if (gVerbose) printf("Error waiting for drain - %s(%d).\n", strerror(errno), errno); } // Traditionally it is good practice to reset a serial port back to // the state in which you found it. This is why the original termios struct // was saved. if (tcsetattr(fileDescriptor, TCSANOW, &gOriginalTTYAttrs) == -1) { if (gVerbose) printf("Error resetting tty attributes - %s(%d).\n", strerror(errno), errno); } close(fileDescriptor); } // -------------------------------------------------------------------------------------------- // register list for RC-80 enum { kRCAddress = 0, kRCCommunicationsMode, kRCSystemOptions, kRCDisplayOptions, kRCCalibrationOffset, kRCCoolLimit, kRCHeatLimit, kRCReserved7, kRCReserved8, kRCCoolingAnticipator, // 10 kRCHeatingAnticipator, kRCCoolingCycleTime, kRCHeatingCycleTime, kRCAuxHeatDifferential, kRCClockAdjust, kRCDaysUntilFilterReminder, kRCRunTimeCurrentWeek, kRCRunTimeLastWeek, kRCRTPSetback, kRCRTPHigh, // 20 kRCRTPCritical, kRCWeekdayMorningTime, kRCWeekdayMorningCool, kRCWeekdayMorningHeat, kRCWeekdayDayTime, kRCWeekdayDayCool, kRCWeekdayDayHeat, kRCWeekdayEveningTime, kRCWeekdayEveningCool, kRCWeekdayEveningHeat, // 30 kRCWeekdayNightTime, kRCWeekdayNightCool, kRCWeekdayNightHeat, kRCSaturdayMorningTime, kRCSaturdayMorningCool, kRCSaturdayMorningHeat, kRCSaturdayDayTime, kRCSaturdayDayCool, kRCSaturdayDayHeat, kRCSaturdayEveningTime, // 40 kRCSaturdayEveningCool, kRCSaturdayEveningHeat, kRCSaturdayNightTime, kRCSaturdayNightCool, kRCSaturdayNightHeat, kRCSundayMorningTime, kRCSundayMorningCool, kRCSundayMorningHeat, kRCSundayDayTime, kRCSundayDayCool, // 50 kRCSundayDayHeat, kRCSundayEveningTime, kRCSundayEveningCool, kRCSundayEveningHeat, kRCSundayNightTime, kRCSundayNightCool, kRCSundayNightHeat, kRCReserved57, kRCDayOfWeek, kRCCurrentCool, // 60 kRCCurrentHeat, kRCCurrentSetMode, kRCCurrentFan, kRCCurrentHold, kRCCurrentTemp, kRCCurrentSeconds, kRCCurrentMinutes, kRCCurrentHours, kRCCurrentOutsideTemp, kRCReserved69, // 70 kRCRTPMode, kRCCurrentActualMode, kRCOutputState, kRCModel }; enum { kRawNumber = 0, kIndexedNumber, kTemperature, kTime, kBinaryNumber }; enum { kCommModeIndex = 0, kSystemOptionIndex, kDayOfWeekIndex, kModeIndex, kFanIndex, kHoldIndex, kRTPMode, kModelIndex }; typedef struct { SInt32 value; unsigned char string[255]; } IndexedValue; IndexedValue gCommModeIndex[] = { {0, "300 baud, RS-232"}, {1, "110 baud, Omni"}, {8, "PESM mode"}, {24, "Day/Night mode"}, {-1, ""} }; IndexedValue gSystemOptionIndex[] = { {0, "Auto changeover, no fan with heat"}, {1, "Auto changeover, fan with heat"}, {4, "Manual changeover, no fan with heat"}, {5, "Manual changeover, fan on with heat"}, {12, "Heat only, no fan with heat"}, {13, "Heat only, fan on with heat"}, {20, "Cool only"}, {-1, ""} }; IndexedValue gDayOfWeekIndex[] = { {0, "Monday"}, {1, "Tuesday"}, {2, "Wednesday"}, {3, "Thursday"}, {4, "Friday"}, {5, "Saturday"}, {6, "Sunday"}, {-1, ""} }; IndexedValue gModeIndex[] = { {0, "Off"}, {1, "Heat"}, {2, "Cool"}, {3, "Auto"}, {4, "Emergency Heat"}, {-1, ""} }; IndexedValue gFanIndex[] = { {0, "Auto"}, {1, "On"}, {-1, ""} }; IndexedValue gHoldIndex[] = { {0, "Off"}, {255, "On"}, {-1, ""} }; IndexedValue gRTPMode[] = { {0, "Low"}, {1, "Mid"}, {2, "High"}, {3, "Critical"}, {-1, ""} }; IndexedValue gModelIndex[] = { {0, "RC-80"}, {1, "RC-81"}, {8, "RC-90"}, {9, "RC-91"}, {16, "RC-100"}, {17, "RC-101"}, {34, "RC-112"}, {48, "RC-120"}, {49, "RC-121"}, {50, "RC-122"}, {-1, ""} }; typedef struct { UInt8 registerNumber; UInt8 dataType; UInt8 min; UInt8 max; unsigned char name[256]; unsigned char label[256]; UInt8 crs; } RegisterInfo; RegisterInfo gRegisterInfo[] = { {kRCAddress, kRawNumber, 1, 127, "Thermostat Address", "", 1}, {kRCCommunicationsMode, kIndexedNumber, kCommModeIndex, 0, "Communications Mode", "", 1}, {kRCSystemOptions, kIndexedNumber, kSystemOptionIndex, 0, "System Options", "", 1}, {kRCDisplayOptions, kBinaryNumber, 0x10, 0x1F, "Display Options", "F/C:24:Non Prog:RTP:Display Off",1}, {kRCCalibrationOffset, kRawNumber, 1, 59, "Calibration offset", "(30 = none, 1/2 C units)", 1}, {kRCCoolLimit, kTemperature, 0, 255, "Minimum Cool limit", "", 1}, {kRCHeatLimit, kTemperature, 0, 255, "Maximum Heat limit", "", 1}, {kRCReserved7, kRawNumber, 0, 255, "Register 7", "(reserved)", 1}, {kRCReserved8, kRawNumber, 0, 255, "Register 8", "(reserved)", 1}, {kRCCoolingAnticipator, kRawNumber, 0, 30, "Cooling Anticipator", "", 1}, {kRCHeatingAnticipator, kRawNumber, 0, 30, "Heating Anticipator", "", 1}, {kRCCoolingCycleTime, kRawNumber, 2, 30, "Cooling Cycle Time", "(minutes)", 1}, {kRCHeatingCycleTime, kRawNumber, 2, 30, "Heating Cycle Time", "(minutes)", 1}, {kRCAuxHeatDifferential, kTemperature, 0, 255, "Aux Heat Differential", "", 1}, {kRCClockAdjust, kRawNumber, 1, 59, "Clock Adjust", "-30 = seconds/day", 1}, {kRCDaysUntilFilterReminder,kRawNumber, 0, 255, "Filter reminder in", "days", 1}, {kRCRunTimeCurrentWeek, kRawNumber, 0, 255, "Run Hours Current Week", "", 1}, {kRCRunTimeLastWeek, kRawNumber, 0, 255, "Run Hours Last Week", "", 1}, {kRCRTPSetback, kTemperature, 0, 255, "Real Time Pricing Setback","", 1}, {kRCRTPHigh, kRawNumber, 0, 255, "Real Time Pricing High", "", 1}, {kRCRTPCritical, kRawNumber, 0, 255, "Real Time Pricing Critical","", 2}, {kRCWeekdayMorningTime, kTime, 0, 96, "Weekday Morning Time", "", 0}, {kRCWeekdayMorningCool, kTemperature, 0, 255, "Weekday Morning Cool", "", 0}, {kRCWeekdayMorningHeat, kTemperature, 0, 255, "Weekday Morning Heat", "", 1}, {kRCWeekdayDayTime, kTime, 0, 96, "Weekday Day Time", "", 0}, {kRCWeekdayDayCool, kTemperature, 0, 255, "Weekday Day Cool", "", 0}, {kRCWeekdayDayHeat, kTemperature, 0, 255, "Weekday Day Heat", "", 1}, {kRCWeekdayEveningTime, kTime, 0, 96, "Weekday Evening Time", "", 0}, {kRCWeekdayEveningCool, kTemperature, 0, 255, "Weekday Evening Cool", "", 0}, {kRCWeekdayEveningHeat, kTemperature, 0, 255, "Weekday Evening Heat", "", 1}, {kRCWeekdayNightTime, kTime, 0, 96, "Weekday Night Time", "", 0}, {kRCWeekdayNightCool, kTemperature, 0, 255, "Weekday Night Cool", "", 0}, {kRCWeekdayNightHeat, kTemperature, 0, 255, "Weekday Night Heat", "", 2}, {kRCSaturdayMorningTime, kTime, 0, 96, "Saturday Morning Time", "", 0}, {kRCSaturdayMorningCool, kTemperature, 0, 255, "Saturday Morning Cool", "", 0}, {kRCSaturdayMorningHeat, kTemperature, 0, 255, "Saturday Morning Heat", "", 1}, {kRCSaturdayDayTime, kTime, 0, 96, "Saturday Day Time", "", 0}, {kRCSaturdayDayCool, kTemperature, 0, 255, "Saturday Day Cool", "", 0}, {kRCSaturdayDayHeat, kTemperature, 0, 255, "Saturday Day Heat", "", 1}, {kRCSaturdayEveningTime, kTime, 0, 96, "Saturday Evening Time", "", 0}, {kRCSaturdayEveningCool, kTemperature, 0, 255, "Saturday Evening Cool", "", 0}, {kRCSaturdayEveningHeat, kTemperature, 0, 255, "Saturday Evening Heat", "", 1}, {kRCSaturdayNightTime, kTime, 0, 96, "Saturday Night Time", "", 0}, {kRCSaturdayNightCool, kTemperature, 0, 255, "Saturday Night Cool", "", 0}, {kRCSaturdayNightHeat, kTemperature, 0, 255, "Saturday Night Heat", "", 2}, {kRCSundayMorningTime, kTime, 0, 96, "Sunday Morning Time", "", 0}, {kRCSundayMorningCool, kTemperature, 0, 255, "Sunday Morning Cool", "", 0}, {kRCSundayMorningHeat, kTemperature, 0, 255, "Sunday Morning Heat", "", 1}, {kRCSundayDayTime, kTime, 0, 96, "Sunday Day Time", "", 0}, {kRCSundayDayCool, kTemperature, 0, 255, "Sunday Day Cool", "", 0}, {kRCSundayDayHeat, kTemperature, 0, 255, "Sunday Day Heat", "", 1}, {kRCSundayEveningTime, kTime, 0, 96, "Sunday Evening Time", "", 0}, {kRCSundayEveningCool, kTemperature, 0, 255, "Sunday Evening Cool", "", 0}, {kRCSundayEveningHeat, kTemperature, 0, 255, "Sunday Evening Heat", "", 1}, {kRCSundayNightTime, kTime, 0, 96, "Sunday Night Time", "", 0}, {kRCSundayNightCool, kTemperature, 0, 255, "Sunday Night Cool", "", 0}, {kRCSundayNightHeat, kTemperature, 0, 255, "Sunday Night Heat", "", 2}, {kRCReserved57, kRawNumber, 0, 255, "Register 57", "(reserved)", 2}, {kRCDayOfWeek, kIndexedNumber, kDayOfWeekIndex, 0, "Day of the Week", "", 1}, {kRCCurrentCool, kTemperature, 0, 255, "Cool", "", 1}, {kRCCurrentHeat, kTemperature, 0, 255, "Heat", "", 1}, {kRCCurrentSetMode, kIndexedNumber, kModeIndex, 0, "Set Mode", "", 1}, {kRCCurrentFan, kIndexedNumber, kFanIndex, 0, "Fan", "", 1}, {kRCCurrentHold, kIndexedNumber, kHoldIndex, 0, "Hold", "", 1}, {kRCCurrentTemp, kTemperature, 0, 255, "Temperature", "", 1}, {kRCCurrentSeconds, kRawNumber, 0, 59, "Seconds", "", 1}, {kRCCurrentMinutes, kRawNumber, 0, 59, "Minutes", "", 1}, {kRCCurrentHours, kRawNumber, 0, 23, "Hours", "", 1}, {kRCCurrentOutsideTemp, kTemperature, 0, 255, "Output Temperature", "", 2}, {kRCReserved69, kRawNumber, 0, 255, "Register 69", "(reserved)", 1}, {kRCRTPMode, kIndexedNumber, kRTPMode, 0, "Real Time Pricing Mode", "", 1}, {kRCCurrentActualMode, kIndexedNumber, kModeIndex, 0, "Actual mode", "", 1}, {kRCOutputState, kBinaryNumber, 0x10, 0x1F, "Run State", "Heat/Cool:Aux:Stage1:Fan:Stage2", 1}, {kRCModel, kIndexedNumber, kModelIndex, 0, "Model", "", 1}, }; // -------------------------------------------------------------------------------------------- static long ThermoTempToDegrees(unsigned char thermoTemp) { // degrees Celsius double temp = -40 + (double)thermoTemp * .5; // round up to match display temp = temp * 9.0 / 5.0 + 32.5; return (temp); } // ThermoTempToDegrees // ------------------------------------------------------------------------------------------ static unsigned char DegreesToThermoTemp(long thermoTemp) { // degrees Celsius double temp = (thermoTemp-31.5) * 5.0 / 9.0; temp += 40; temp *= 2; return (temp); } // DegreesToThermoTemp // ------------------------------------------------------------------------------------------ static unsigned char TimeToValue(long time) { LongDateTime dateTimeC; LongDateCvt dateTime; LongDateRec longDateTime; long result; dateTime.hl.lLow = time; dateTime.hl.lHigh = 0; dateTimeC = dateTime.c; LongSecondsToDate(&dateTimeC, &longDateTime); // midnight should be 00 if (longDateTime.ld.hour == 24) longDateTime.ld.hour = 0; // round to the nearest 15 minute increment longDateTime.ld.minute /= 15; result = longDateTime.ld.hour * 4 + longDateTime.ld.minute; return result; } // TimeToValue // ------------------------------------------------------------------------------------------ static long HoursAndMinutesToTime(long hours, long minutes) { LongDateTime dateTimeC; LongDateCvt dateTime; LongDateRec longDateTime; GetDateTime(&dateTime.hl.lLow); dateTime.hl.lHigh = 0; dateTimeC = dateTime.c; LongSecondsToDate(&dateTimeC, &longDateTime); longDateTime.ld.hour = hours; longDateTime.ld.minute = minutes; longDateTime.ld.second = 0; longDateTime.ld.pm = 0; LongDateToSeconds( &longDateTime, &dateTimeC); dateTime.c = dateTimeC; return dateTime.hl.lLow; } // HoursAndMinutesToTime // -------------------------------------------------------------------------------------------- static void PrintOneValue(UInt8 value, UInt8 registerIndex) { Boolean outOfRange = false; UInt32 chars = 0; chars += printf("%s: ", gRegisterInfo[registerIndex].name); { short neededChars; char cr; neededChars = 28 - chars; for (cr = 0; cr < neededChars; ++cr) chars += printf(" "); } switch (gRegisterInfo[registerIndex].dataType) { case kRawNumber: chars += printf("%d", value); if ((value < gRegisterInfo[registerIndex].min) || (value > gRegisterInfo[registerIndex].max)) { chars += printf(" (out of range)"); outOfRange = true; } break; case kIndexedNumber: { IndexedValue *indexList = nil; switch (gRegisterInfo[registerIndex].min) { case kCommModeIndex: indexList = &gCommModeIndex[0]; break; case kSystemOptionIndex: indexList = &gSystemOptionIndex[0]; break; case kDayOfWeekIndex: indexList = &gDayOfWeekIndex[0]; break; case kModeIndex: indexList = &gModeIndex[0]; break; case kFanIndex: indexList = &gFanIndex[0]; break; case kHoldIndex: indexList = &gHoldIndex[0]; break; case kRTPMode: indexList = &gRTPMode[0]; break; case kModelIndex: indexList = &gModelIndex[0]; break; } if (indexList) { SInt16 index; index = 0; while (indexList[index].value != -1) { if (indexList[index].value == value) break; index++; } if (indexList[index].value != -1) { chars += printf("%s", indexList[index].string); } else { chars += printf("%d (out of range)", value); outOfRange = true; } } else { chars += printf("Invalid index list %d for value %d", gRegisterInfo[registerIndex].min, value); outOfRange = true; } } break; case kTemperature: if ((value < gRegisterInfo[registerIndex].min) || (value > gRegisterInfo[registerIndex].max)) { chars += printf("%d (*)", value); outOfRange = true; } else { chars += printf("%dĄ F", ThermoTempToDegrees(value)); } break; case kTime: if ((value < gRegisterInfo[registerIndex].min) || (value > gRegisterInfo[registerIndex].max)) chars += printf("%d (*)"); else { if (value == 96) chars += printf("(unused)"); else chars += printf("%02d:%02d", value / 4, 15 * (value % 4)); } break; case kBinaryNumber: if (value > gRegisterInfo[registerIndex].max) { chars += printf("%08X (out of range)", value); outOfRange = true; } else { UInt8 mask; mask = gRegisterInfo[registerIndex].min; while (mask) { if (value & mask) chars += printf("1"); else chars += printf("0"); mask >>= 1; } } break; default: chars += printf("Unknown format %d for value %d\n", gRegisterInfo[registerIndex].dataType, value); break; } if (outOfRange == false) chars += printf(" %s", gRegisterInfo[registerIndex].label); { short cr; if (gRegisterInfo[registerIndex].crs == 0) { short neededChars; neededChars = 45 - chars; for (cr = 0; cr < neededChars; ++cr) chars += printf(" "); } else { for (cr = 0; cr < gRegisterInfo[registerIndex].crs; ++cr) chars += printf("\n"); } } } // -------------------------------------------------------------------------------------------- static void PrintRegisterValues(UInt8 *registers, UInt8 startingRegister, UInt8 count) { short i; if (gVerbose) printf("Register dump from %d to %d\n\n", startingRegister, startingRegister + count); for (i = 0; i < count; ++i) { UInt8 value; UInt8 registerIndex; value = registers[i]; registerIndex = i + startingRegister; PrintOneValue(value, registerIndex); } } typedef struct { // weekday settings long weekdayMorning; long weekdayMorningCool; long weekdayMorningHeat; long weekdayDay; long weekdayDayCool; long weekdayDayHeat; long weekdayEvening; long weekdayEveningCool; long weekdayEveningHeat; long weekdayNight; long weekdayNightCool; long weekdayNightHeat; // Saturday settings long saturdayMorning; long saturdayMorningCool; long saturdayMorningHeat; long saturdayDay; long saturdayDayCool; long saturdayDayHeat; long saturdayEvening; long saturdayEveningCool; long saturdayEveningHeat; long saturdayNight; long saturdayNightCool; long saturdayNightHeat; // Sunday settings long sundayMorning; long sundayMorningCool; long sundayMorningHeat; long sundayDay; long sundayDayCool; long sundayDayHeat; long sundayEvening; long sundayEveningCool; long sundayEveningHeat; long sundayNight; long sundayNightCool; long sundayNightHeat; } TempSetpoints; // ------------------------------------------------------------------------------------------ static void DefaultSetpoints(TempSetpoints *pSetpoints) { // weekday pSetpoints->weekdayMorning = HoursAndMinutesToTime(6, 00); pSetpoints->weekdayMorningCool = 85; pSetpoints->weekdayMorningHeat = 65; pSetpoints->weekdayDay = HoursAndMinutesToTime(10, 00); pSetpoints->weekdayDayCool = 85; pSetpoints->weekdayDayHeat = 62; pSetpoints->weekdayEvening = HoursAndMinutesToTime(17, 00); pSetpoints->weekdayEveningCool = 85; pSetpoints->weekdayEveningHeat = 65; pSetpoints->weekdayNight = HoursAndMinutesToTime(22, 00); pSetpoints->weekdayNightCool = 85; pSetpoints->weekdayNightHeat = 62; // saturday pSetpoints->saturdayMorning = HoursAndMinutesToTime(6, 00); pSetpoints->saturdayMorningCool = 85; pSetpoints->saturdayMorningHeat = 65; pSetpoints->saturdayDay = HoursAndMinutesToTime(10, 00); pSetpoints->saturdayDayCool = 85; pSetpoints->saturdayDayHeat = 62; pSetpoints->saturdayEvening = HoursAndMinutesToTime(17, 00); pSetpoints->saturdayEveningCool = 85; pSetpoints->saturdayEveningHeat = 65; pSetpoints->saturdayNight = HoursAndMinutesToTime(22, 00); pSetpoints->saturdayNightCool = 85; pSetpoints->saturdayNightHeat = 62; // sunday pSetpoints->sundayMorning = HoursAndMinutesToTime(6, 00); pSetpoints->sundayMorningCool = 85; pSetpoints->sundayMorningHeat = 65; pSetpoints->sundayDay = HoursAndMinutesToTime(10, 00); pSetpoints->sundayDayCool = 85; pSetpoints->sundayDayHeat = 62; pSetpoints->sundayEvening = HoursAndMinutesToTime(17, 00); pSetpoints->sundayEveningCool = 85; pSetpoints->sundayEveningHeat = 65; pSetpoints->sundayNight = HoursAndMinutesToTime(22, 00); pSetpoints->sundayNightCool = 85; pSetpoints->sundayNightHeat = 62; } // DefaultSetpoints // ------------------------------------------------------------------------------------------ #define READTIMEOUT (60) // might take up to a second to read the data at 300 baud #define RETRIES (3) #define WRITEWAIT (90) // 1.5 seconds between attempts #define THERMOSTATADDRESS 0x01 // ------------------------------------------------------------------------------------------ static Boolean ChecksumIsValid(long count, unsigned char *pInputBuffer) { unsigned char sum = 0; long i; // must be a "from the thermostat" message if ( ((pInputBuffer[0] & 0xF0) != 0x80) || ((pInputBuffer[0] & 0x7F) != THERMOSTATADDRESS) ) return false; for (i = 0; i < count-1; ++i) sum += pInputBuffer[i]; if (sum == pInputBuffer[count-1]) return true; else return false; } // ChecksumIsValid // ------------------------------------------------------------------------------------------ static unsigned char CalculateChecksum(long count, unsigned char *pInputBuffer) { unsigned char sum = 0; long i; for (i = 0; i < count; ++i) sum += pInputBuffer[i]; return sum; } // CalculateChecksum // ------------------------------------------------------------------------------------------ static Boolean WriteRegisters(int fileDescriptor, short firstRegister, short numToWrite, unsigned char* pRegisterData) { unsigned char buffer[200]; short i; long timeout; long retries = RETRIES; long count; Boolean gotOne = false; doIt: // clear out the buffer count = read(fileDescriptor, buffer, sizeof(buffer)); // write out the data buffer[0] = THERMOSTATADDRESS; buffer[1] = ((numToWrite+1) << 4) | 0x1; buffer[2] = firstRegister; for (i = 0; i < numToWrite; ++i) buffer[3+i] = pRegisterData[i]; buffer[3+numToWrite] = CalculateChecksum(numToWrite + 3, buffer); count = write(fileDescriptor, buffer, 4+numToWrite); // wait for and read the result timeout = TickCount() + READTIMEOUT; count = 0; while (true) { long readCount; readCount = read(fileDescriptor, &buffer[count], sizeof(buffer) - count); if (readCount != -1) count += readCount; if (count >= 3) { // three bytes is either a NAK or an ACK if ( (count == 3) && (ChecksumIsValid(count, buffer)) && (buffer[1] == 0) ) gotOne = true; } if (TickCount() > timeout) break; } // need to retry? if (!gotOne) { retries--; if (retries) { unsigned long actual; Delay(WRITEWAIT, &actual); goto doIt; } } if (!gotOne) { if (gVerbose) printf("Failed to perform register write\n"); } return gotOne; } // WriteRegisters // ------------------------------------------------------------------------------------------ static Boolean ReadRegisters(int fileDescriptor, short firstToRead, short numberToRead, unsigned char* pBuffer) { long count; long timeout; Boolean gotOne = false; long retries = RETRIES; unsigned char buffer[200]; doIt: // clear out the buffer count = read(fileDescriptor, buffer, sizeof(buffer)); // write the request count = 0; buffer[count++] = THERMOSTATADDRESS; buffer[count++] = ((2) << 4) | 0x0; buffer[count++] = firstToRead; buffer[count++] = numberToRead; buffer[count] = CalculateChecksum(count, buffer); count++; write(fileDescriptor, buffer, count); // wait for and read the result timeout = TickCount() + READTIMEOUT; count = 0; while (true) { long readCount; readCount = read(fileDescriptor, &buffer[count], sizeof(buffer) - count); if (readCount != -1) count += readCount; if (count >= numberToRead+4) { if ((count == numberToRead+4) && (ChecksumIsValid(count, buffer))) { if (buffer[2] == firstToRead) BlockMove(&buffer[3], &pBuffer[0], numberToRead); gotOne = true; } } if (TickCount() > timeout) break; } // need to retry? if (!gotOne) { retries--; if (retries) { unsigned long actual; Delay(WRITEWAIT, &actual); goto doIt; } } if (!gotOne) { if (gVerbose) printf("Failed to perform register read\n"); } return gotOne; } // ReadRegisters // ------------------------------------------------------------------------------------------ static Boolean SetTempSetpoints(int fileDescriptor, TempSetpoints *pSetpoints) { unsigned char buffer[4*3*3]; long count = 0; // weekday buffer[count++] = TimeToValue( pSetpoints->weekdayMorning); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayMorningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayMorningHeat); buffer[count++] = TimeToValue( pSetpoints->weekdayDay); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayDayCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayDayHeat); buffer[count++] = TimeToValue( pSetpoints->weekdayEvening); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayEveningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayEveningHeat); buffer[count++] = TimeToValue( pSetpoints->weekdayNight); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayNightCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->weekdayNightHeat); // saturday buffer[count++] = TimeToValue( pSetpoints->saturdayMorning); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayMorningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayMorningHeat); buffer[count++] = TimeToValue( pSetpoints->saturdayDay); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayDayCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayDayHeat); buffer[count++] = TimeToValue( pSetpoints->saturdayEvening); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayEveningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayEveningHeat); buffer[count++] = TimeToValue( pSetpoints->saturdayNight); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayNightCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->saturdayNightHeat); // sunday buffer[count++] = TimeToValue( pSetpoints->sundayMorning); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayMorningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayMorningHeat); buffer[count++] = TimeToValue( pSetpoints->sundayDay); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayDayCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayDayHeat); buffer[count++] = TimeToValue( pSetpoints->sundayEvening); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayEveningCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayEveningHeat); buffer[count++] = TimeToValue( pSetpoints->sundayNight); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayNightCool); buffer[count++] = DegreesToThermoTemp( pSetpoints->sundayNightHeat); return WriteRegisters(fileDescriptor, kRCWeekdayMorningTime, count, buffer); } // SetTempSetpoints // -------------------------------------------------------------------------------------------- enum { kDayPeriodWeekday = 0, kDayPeriodSaturday = 1, kDayPeriodSunday = 2 }; enum { kTimePeriodMorning = 0, kTimePeriodDay = 1, kTimePeriodEvening = 2, kTimePeriodNight = 3 }; enum { kDataTime = 0, kDataCool = 1, kDataHeat = 2 }; typedef struct { UInt32 dayPeriod; UInt32 timePeriod; UInt32 dataType; TempSetpoints setPoints; char buffer[2048]; } XMLParseData; // -------------------------------------------------------------------------------------------- static pascal ComponentResult XMLStartElementHandler (const char *name, const char **atts, long refcon) { XMLParseData *pData = (XMLParseData*)refcon; OSStatus status = fnfErr; (void) atts; if (strcmp(name, "setpoints") == 0) { pData->dayPeriod = -1; pData->timePeriod = -1; pData->dataType = -1; status = noErr; } // days else if (strcmp(name, "weekday") == 0) { pData->dayPeriod = kDayPeriodWeekday; pData->timePeriod = -1; pData->dataType = -1; status = noErr; } else if (strcmp(name, "saturday") == 0) { pData->dayPeriod = kDayPeriodSaturday; pData->timePeriod = -1; pData->dataType = -1; status = noErr; } else if (strcmp(name, "sunday") == 0) { pData->dayPeriod = kDayPeriodSunday; pData->timePeriod = -1; pData->dataType = -1; status = noErr; } // time periods else if (strcmp(name, "morning") == 0) { pData->timePeriod = kTimePeriodMorning; pData->dataType = -1; status = noErr; } else if (strcmp(name, "day") == 0) { pData->timePeriod = kTimePeriodDay; pData->dataType = -1; status = noErr; } else if (strcmp(name, "evening") == 0) { pData->timePeriod = kTimePeriodEvening; pData->dataType = -1; status = noErr; } else if (strcmp(name, "night") == 0) { pData->timePeriod = kTimePeriodNight; pData->dataType = -1; status = noErr; } // data within each time period else if (strcmp(name, "time") == 0) { pData->dataType = kDataTime; status = noErr; } else if (strcmp(name, "cool") == 0) { pData->dataType = kDataCool; status = noErr; } else if (strcmp(name, "heat") == 0) { pData->dataType = kDataHeat; status = noErr; } return status; } // XMLStartElementHandler // -------------------------------------------------------------------------------------------- static pascal ComponentResult XMLEndElementHandler (const char *name, long refcon) { OSStatus status = paramErr; XMLParseData *pData = (XMLParseData*)refcon; if ((pData->dayPeriod != -1) && (pData->timePeriod != -1) && (pData->dataType != -1)) { long *dataDestination; // assume everything goes okay from this point status = noErr; dataDestination = (long*)&pData->setPoints.weekdayMorning; dataDestination += (pData->dayPeriod * 12) + (pData->timePeriod * 3) + pData->dataType; if (strcmp(name, "time") == 0) { long hours, minutes; sscanf(pData->buffer, "%ld:%ld", &hours, &minutes); *dataDestination= HoursAndMinutesToTime(hours, minutes); } else if (strcmp(name, "cool") == 0) { sscanf(pData->buffer, "%ld", dataDestination); } else if (strcmp(name, "heat") == 0) { sscanf(pData->buffer, "%ld", dataDestination); } pData->buffer[0] = 0; } return status; } // XMLEndElementHandler // -------------------------------------------------------------------------------------------- static pascal ComponentResult XMLCharDataHandler (const char *charData, long refcon) { XMLParseData *pData = (XMLParseData*)refcon; strncat(pData->buffer, charData, sizeof(pData->buffer)); return noErr; } // XMLCharDataHandler // -------------------------------------------------------------------------------------------- int main(int argc, char *argv[]) { UInt8 status[74]; int result = 0; io_iterator_t serialPortIterator; char bsdPath[2048]; if (argc <= 1) { printf("Usage: rc80 -[dvcrwt] [-p ] [-o ]\n"); printf(" -d : display thermostat registers\n"); printf(" -v : verbose/debugging mode\n"); printf(" -c : output current temperature\n"); printf(" -r : read and output current setpoints\n"); printf(" -w : write current setpoints from setpoints.xml file\n"); printf(" -o : set current outside temperature\n"); printf(" -t : set current time\n"); printf(" -p : serial port name to use\n"); } else { int argument; Boolean displayRegisters = false; Boolean outputTemperature = false; Boolean readSetpoints = false; Boolean writeSetpoints = false; Boolean setTemperature = false; long outsideTemperature = -200; Boolean setTime = false; char serialPort[256] = "\0"; // parse arguments to determine what we need to do result = 0; argument = 1; while (argument < argc) { if (strcmp(argv[argument], "-d") == 0) { displayRegisters = true; } else if (strcmp(argv[argument], "-v") == 0) { gVerbose = true; } else if (strcmp(argv[argument], "-c") == 0) { outputTemperature = true; } else if (strcmp(argv[argument], "-r") == 0) { readSetpoints = true; } else if (strcmp(argv[argument], "-w") == 0) { writeSetpoints = true; } else if (strcmp(argv[argument], "-t") == 0) { setTime = true; } else if (strcmp(argv[argument], "-o") == 0) { argument++; if (argument <= argc) sscanf(argv[argument], "%ld", &outsideTemperature); if (outsideTemperature > -200) setTemperature = true; } else if (strcmp(argv[argument], "-p") == 0) { argument++; if (argument <= argc) sscanf(argv[argument], "%s", serialPort); } argument++; } if (result == 0) { EnterMovies(); memset(status, 0, sizeof(status)); result = FindSerialPorts(&serialPortIterator); if (result == KERN_SUCCESS) { result = GetSerialPath(serialPortIterator, bsdPath, sizeof(bsdPath), serialPort); IOObjectRelease(serialPortIterator); // Release the iterator. if (result == KERN_SUCCESS) { int fileDescriptor; fileDescriptor = OpenSerialPort(bsdPath); if (-1 != fileDescriptor) { unsigned char buffer[20]; // -t if (setTime) { LongDateTime dateTimeC; LongDateCvt dateTime; LongDateRec longDateTime; // get the current time GetDateTime(&dateTime.hl.lLow); dateTime.hl.lHigh = 0; dateTimeC = dateTime.c; LongSecondsToDate(&dateTimeC, &longDateTime); // set the time buffer[0] = longDateTime.ld.second; buffer[1] = longDateTime.ld.minute; buffer[2] = longDateTime.ld.hour; if (WriteRegisters(fileDescriptor, kRCCurrentSeconds, 3, buffer) == false) { if (gVerbose) printf("Failed to set time\n"); result = KERN_FAILURE; } // set the day of week -- day of week is 1-7 starting on Sunday, but we want 0-6 starting on Monday longDateTime.ld.dayOfWeek -= 2; if (longDateTime.ld.dayOfWeek == -1) longDateTime.ld.dayOfWeek = 6; buffer[0] = longDateTime.ld.dayOfWeek; if (WriteRegisters(fileDescriptor, kRCDayOfWeek, 1, buffer) == false) { if (gVerbose) printf("Failed to set day of week\n"); result = KERN_FAILURE; } } // write current time and day of week // -o if (setTemperature) { buffer[0] = DegreesToThermoTemp(outsideTemperature); if (WriteRegisters(fileDescriptor, kRCCurrentOutsideTemp, 1, buffer) == false) { if (gVerbose) printf("Failed to set outside temperature\n"); result = KERN_FAILURE; } } // write current outside temp // -w if (writeSetpoints) { XMLParseData parseData; int fileSize; FILE *xmlFile; CFBundleRef bundle; CFURLRef url; CFStringRef urlString; char path[2048]; // determine where our app is, open a file located next to it bundle = CFBundleGetMainBundle(); url = CFBundleCopyBundleURL(bundle); urlString = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); CFStringGetCString(urlString, path, sizeof(path), kCFStringEncodingMacRoman); CFRelease(urlString); CFRelease(url); strcat(path, "/setpoints.xml"); xmlFile = fopen(path, "r"); if (NULL != xmlFile) { Handle fileContents; ComponentInstance ci; fseek(xmlFile, 0, SEEK_END); fileSize = ftell(xmlFile); rewind(xmlFile); fileContents = NewHandle(fileSize); HLock(fileContents); fileSize = fread(*fileContents, 1, fileSize, xmlFile); fclose(xmlFile); DefaultSetpoints(&parseData.setPoints); ci = OpenDefaultComponent(xmlParseComponentType, xmlParseComponentSubType); if (ci) { Handle dataHandle = NewHandle(sizeof(Handle)); **(Handle**)dataHandle = fileContents; parseData.buffer[0] = 0; XMLParseSetEventParseRefCon(ci, (long)&parseData); XMLParseSetStartElementHandler(ci, XMLStartElementHandler); XMLParseSetEndElementHandler(ci, XMLEndElementHandler); XMLParseSetCharDataHandler(ci, XMLCharDataHandler); if (XMLParseDataRef(ci, dataHandle, HandleDataHandlerSubType, xmlParseFlagEventParseOnly, nil) != noErr) { if (gVerbose) printf("Failed to parse setpoint xml file\n"); result = KERN_FAILURE; } DisposeHandle(dataHandle); CloseComponent(ci); } else { result = KERN_FAILURE; } DisposeHandle(fileContents); if (SetTempSetpoints(fileDescriptor, &parseData.setPoints) == false) { if (gVerbose) printf("Failed to set setpoints\n"); result = KERN_FAILURE; } } else { if (gVerbose) printf("Failed to open setpoint file\n"); result = KERN_FAILURE; } } // write setpoints // read in the register list after any/all of our changes have taken place { short i; for (i = 0; i < 74; i += 14) { short count; count = 14; if (i + count > 74) count = 74 - i; if (ReadRegisters(fileDescriptor, i, count, &status[i]) == false) { result = KERN_FAILURE; break; } } } CloseSerialPort(fileDescriptor); } else { result = KERN_FAILURE; } } } // finally, output results based upon what we read in. // -r if (readSetpoints) PrintRegisterValues(status, kRCWeekdayMorningTime, kRCSundayNightHeat - kRCWeekdayMorningTime+1); // -c if (outputTemperature) { UInt8 value = status[kRCCurrentTemp]; printf("%d\n", ThermoTempToDegrees(value)); } // -d if (displayRegisters) PrintRegisterValues(status, 0, 74); ExitMovies(); } } return result; }