moel@171: /* moel@171: moel@171: Version: MPL 1.1/GPL 2.0/LGPL 2.1 moel@171: moel@171: The contents of this file are subject to the Mozilla Public License Version moel@171: 1.1 (the "License"); you may not use this file except in compliance with moel@171: the License. You may obtain a copy of the License at moel@171: moel@171: http://www.mozilla.org/MPL/ moel@171: moel@171: Software distributed under the License is distributed on an "AS IS" basis, moel@171: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License moel@171: for the specific language governing rights and limitations under the License. moel@171: moel@171: The Original Code is the Open Hardware Monitor code. moel@171: moel@171: The Initial Developer of the Original Code is moel@171: Michael Möller . moel@261: Portions created by the Initial Developer are Copyright (C) 2010-2011 moel@171: the Initial Developer. All Rights Reserved. moel@171: moel@171: Contributor(s): moel@171: moel@171: Alternatively, the contents of this file may be used under the terms of moel@171: either the GNU General Public License Version 2 or later (the "GPL"), or moel@171: the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), moel@171: in which case the provisions of the GPL or the LGPL are applicable instead moel@171: of those above. If you wish to allow use of your version of this file only moel@171: under the terms of either the GPL or the LGPL, and not to allow others to moel@171: use your version of this file under the terms of the MPL, indicate your moel@171: decision by deleting the provisions above and replace them with the notice moel@171: and other provisions required by the GPL or the LGPL. If you do not delete moel@171: the provisions above, a recipient may use your version of this file under moel@171: the terms of any one of the MPL, the GPL or the LGPL. moel@171: moel@171: */ moel@171: moel@171: using System; moel@182: using System.Globalization; moel@171: using System.IO; moel@171: using System.IO.Ports; moel@171: using System.Text; moel@171: using System.Text.RegularExpressions; moel@171: using System.Threading; moel@171: moel@171: namespace OpenHardwareMonitor.Hardware.Heatmaster { moel@182: internal class Heatmaster : Hardware, IDisposable { moel@171: moel@195: private readonly string portName; moel@171: private SerialPort serialPort; moel@171: moel@195: private readonly int hardwareRevision; moel@195: private readonly int firmwareRevision; moel@195: private readonly int firmwareCRC; moel@171: moel@195: private readonly Sensor[] fans; moel@195: private readonly Sensor[] controls; moel@195: private readonly Sensor[] temperatures; moel@195: private readonly Sensor[] flows; moel@195: private readonly Sensor[] relays; moel@171: moel@195: private readonly bool available; moel@171: moel@195: private readonly StringBuilder buffer = new StringBuilder(); moel@172: moel@171: private string ReadLine(int timeout) { moel@171: int i = 0; moel@171: StringBuilder builder = new StringBuilder(); moel@171: while (i <= timeout) { moel@171: while (serialPort.BytesToRead > 0) { moel@171: byte b = (byte)serialPort.ReadByte(); moel@171: switch (b) { moel@171: case 0xAA: return ((char)b).ToString(); moel@171: case 0x0D: return builder.ToString(); moel@171: default: builder.Append((char)b); break; moel@171: } moel@171: } moel@171: i++; moel@171: Thread.Sleep(1); moel@171: } moel@171: throw new TimeoutException(); moel@171: } moel@171: moel@171: private string ReadField(int device, char field) { moel@171: serialPort.WriteLine("[0:" + device + "]R" + field); moel@171: for (int i = 0; i < 5; i++) { moel@172: string s = ReadLine(200); moel@182: Match match = Regex.Match(s, @"-\[0:" + moel@182: device.ToString(CultureInfo.InvariantCulture) + @"\]R" + moel@182: Regex.Escape(field.ToString(CultureInfo.InvariantCulture)) + ":(.*)"); moel@171: if (match.Success) moel@171: return match.Groups[1].Value; moel@171: } moel@171: return null; moel@171: } moel@171: moel@195: protected string ReadString(int device, char field) { moel@171: string s = ReadField(device, field); moel@171: if (s != null && s[0] == '"' && s[s.Length - 1] == '"') moel@171: return s.Substring(1, s.Length - 2); moel@171: else moel@171: return null; moel@171: } moel@171: moel@195: protected int ReadInteger(int device, char field) { moel@171: string s = ReadField(device, field); moel@171: int i; moel@171: if (int.TryParse(s, out i)) moel@171: return i; moel@171: else moel@171: return 0; moel@171: } moel@171: moel@171: private bool WriteField(int device, char field, string value) { moel@171: serialPort.WriteLine("[0:" + device + "]W" + field + ":" + value); moel@171: for (int i = 0; i < 5; i++) { moel@172: string s = ReadLine(200); moel@182: Match match = Regex.Match(s, @"-\[0:" + moel@182: device.ToString(CultureInfo.InvariantCulture) + @"\]W" + moel@182: Regex.Escape(field.ToString(CultureInfo.InvariantCulture)) + moel@182: ":" + value); moel@171: if (match.Success) moel@171: return true; moel@171: } moel@171: return false; moel@171: } moel@171: moel@195: protected bool WriteInteger(int device, char field, int value) { moel@182: return WriteField(device, field, moel@182: value.ToString(CultureInfo.InvariantCulture)); moel@171: } moel@171: moel@195: protected bool WriteString(int device, char field, string value) { moel@171: return WriteField(device, field, '"' + value + '"'); moel@171: } moel@171: moel@171: public Heatmaster(string portName, ISettings settings) { moel@171: moel@171: this.portName = portName; moel@171: try { moel@171: serialPort = new SerialPort(portName, 38400, Parity.None, 8, moel@171: StopBits.One); moel@171: serialPort.Open(); moel@171: serialPort.NewLine = ((char)0x0D).ToString(); moel@171: moel@171: hardwareRevision = ReadInteger(0, 'H'); moel@171: firmwareRevision = ReadInteger(0, 'V'); moel@171: firmwareCRC = ReadInteger(0, 'C'); moel@171: moel@173: int fanCount = Math.Min(ReadInteger(32, '?'), 4); moel@173: int temperatureCount = Math.Min(ReadInteger(48, '?'), 6); moel@173: int flowCount = Math.Min(ReadInteger(64, '?'), 1); moel@173: int relayCount = Math.Min(ReadInteger(80, '?'), 1); moel@171: moel@171: fans = new Sensor[fanCount]; moel@171: controls = new Sensor[fanCount]; moel@171: for (int i = 0; i < fanCount; i++) { moel@171: int device = 33 + i; moel@171: string name = ReadString(device, 'C'); moel@171: fans[i] = new Sensor(name, device, SensorType.Fan, this, settings); moel@171: fans[i].Value = ReadInteger(device, 'R'); moel@171: ActivateSensor(fans[i]); moel@171: controls[i] = moel@171: new Sensor(name, device, SensorType.Control, this, settings); moel@171: controls[i].Value = (100 / 255.0f) * ReadInteger(device, 'P'); moel@171: ActivateSensor(controls[i]); moel@195: } moel@171: moel@171: temperatures = new Sensor[temperatureCount]; moel@171: for (int i = 0; i < temperatureCount; i++) { moel@171: int device = 49 + i; moel@171: string name = ReadString(device, 'C'); moel@171: temperatures[i] = moel@171: new Sensor(name, device, SensorType.Temperature, this, settings); moel@171: int value = ReadInteger(device, 'T'); moel@171: temperatures[i].Value = 0.1f * value; moel@171: if (value != -32768) moel@171: ActivateSensor(temperatures[i]); moel@171: } moel@171: moel@171: flows = new Sensor[flowCount]; moel@171: for (int i = 0; i < flowCount; i++) { moel@171: int device = 65 + i; moel@171: string name = ReadString(device, 'C'); moel@171: flows[i] = new Sensor(name, device, SensorType.Flow, this, settings); moel@171: flows[i].Value = 0.1f * ReadInteger(device, 'L'); moel@171: ActivateSensor(flows[i]); moel@171: } moel@171: moel@171: relays = new Sensor[relayCount]; moel@171: for (int i = 0; i < relayCount; i++) { moel@171: int device = 81 + i; moel@171: string name = ReadString(device, 'C'); moel@171: relays[i] = moel@171: new Sensor(name, device, SensorType.Control, this, settings); moel@171: relays[i].Value = 100 * ReadInteger(device, 'S'); moel@171: ActivateSensor(relays[i]); moel@171: } moel@171: moel@171: // set the update rate to 2 Hz moel@171: WriteInteger(0, 'L', 2); moel@171: moel@171: available = true; moel@171: moel@171: } catch (IOException) { } catch (TimeoutException) { } moel@171: } moel@171: moel@171: public override HardwareType HardwareType { moel@171: get { return HardwareType.Heatmaster; } moel@171: } moel@171: moel@171: public override Identifier Identifier { moel@171: get { moel@171: return new Identifier("heatmaster", moel@195: serialPort.PortName.TrimStart(new [] {'/'}).ToLowerInvariant()); moel@171: } moel@171: } moel@171: moel@171: public override string Name { moel@171: get { return "Heatmaster"; } moel@171: } moel@171: moel@172: private void ProcessUpdateLine(string line) { moel@172: Match match = Regex.Match(line, @">\[0:(\d+)\]([0-9:\|-]+)"); moel@172: if (match.Success) { moel@172: int device; moel@172: if (int.TryParse(match.Groups[1].Value, out device)) { moel@172: foreach (string s in match.Groups[2].Value.Split('|')) { moel@172: string[] strings = s.Split(':'); moel@260: int[] ints = new int[strings.Length]; moel@260: bool valid = true; moel@172: for (int i = 0; i < ints.Length; i++) moel@260: if (!int.TryParse(strings[i], out ints[i])) { moel@260: valid = false; moel@260: break; moel@260: } moel@260: if (!valid) moel@260: continue; moel@172: switch (device) { moel@172: case 32: moel@172: if (ints.Length == 3 && ints[0] <= fans.Length) { moel@172: fans[ints[0] - 1].Value = ints[1]; moel@172: controls[ints[0] - 1].Value = (100 / 255.0f) * ints[2]; moel@172: } moel@172: break; moel@172: case 48: moel@172: if (ints.Length == 2 && ints[0] <= temperatures.Length) moel@172: temperatures[ints[0] - 1].Value = 0.1f * ints[1]; moel@172: break; moel@172: case 64: moel@172: if (ints.Length == 3 && ints[0] <= flows.Length) moel@172: flows[ints[0] - 1].Value = 0.1f * ints[1]; moel@172: break; moel@172: case 80: moel@172: if (ints.Length == 2 && ints[0] <= relays.Length) moel@172: relays[ints[0] - 1].Value = 100 * ints[1]; moel@172: break; moel@172: } moel@172: } moel@172: } moel@172: } moel@172: } moel@172: moel@171: public override void Update() { moel@171: if (!available) moel@171: return; moel@171: moel@261: while (serialPort.IsOpen && serialPort.BytesToRead > 0) { moel@172: byte b = (byte)serialPort.ReadByte(); moel@172: if (b == 0x0D) { moel@172: ProcessUpdateLine(buffer.ToString()); moel@172: buffer.Length = 0; moel@172: } else { moel@172: buffer.Append((char)b); moel@171: } moel@171: } moel@171: } moel@171: moel@171: public override string GetReport() { moel@171: StringBuilder r = new StringBuilder(); moel@171: moel@171: r.AppendLine("Heatmaster"); moel@171: r.AppendLine(); moel@171: r.Append("Port: "); moel@171: r.AppendLine(portName); moel@171: r.Append("Hardware Revision: "); moel@182: r.AppendLine(hardwareRevision.ToString(CultureInfo.InvariantCulture)); moel@171: r.Append("Firmware Revision: "); moel@182: r.AppendLine(firmwareRevision.ToString(CultureInfo.InvariantCulture)); moel@171: r.Append("Firmware CRC: "); moel@182: r.AppendLine(firmwareCRC.ToString(CultureInfo.InvariantCulture)); moel@171: r.AppendLine(); moel@171: moel@171: return r.ToString(); moel@171: } moel@171: moel@171: public void Close() { moel@171: serialPort.Close(); moel@182: serialPort.Dispose(); moel@182: serialPort = null; moel@182: } moel@182: moel@182: public void Dispose() { moel@182: if (serialPort != null) { moel@182: serialPort.Dispose(); moel@182: serialPort = null; moel@182: } moel@171: } moel@171: } moel@171: }