// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.IO.Ports; using System.ServiceModel; namespace Spludlow.Io { public class X10 : IX10 //, IDisposable { public enum HouseCode : byte { A = 0x06, B = 0x0E, C = 0x02, D = 0x0A, E = 0x01, F = 0x09, G = 0x05, H = 0x0D, I = 0x07, J = 0x0F, K = 0x03, L = 0x0B, M = 0X00, N = 0x08, O = 0x04, P = 0x0C } public enum DeviceCode : byte { D01 = 0x06, D02 = 0x0E, D03 = 0x02, D04 = 0x0A, D05 = 0x01, D06 = 0x09, D07 = 0x05, D08 = 0x0D, D09 = 0x07, D10 = 0x0F, D11 = 0x03, D12 = 0x0B, D13 = 0X00, D14 = 0x08, D15 = 0x04, D16 = 0x0C } public enum Function : byte { AllUnitsOff = 0x00, AllLightsOn = 0x01, On = 0x02, Off = 0x03, Dim = 0x04, Bright = 0x05, AllLightsOff = 0x06, ExtendedCode = 0x07, HailRequest = 0x08, HailAcknowledge = 0x09, PresetDim1 = 0x0A, PresetDim2 = 0x0B, ExtededDataTransfer = 0x0C, StatusOn = 0X0D, StatusOff = 0x0E, StatusRequest = 0x0F } public class Address { public HouseCode HouseCode; public DeviceCode DeviceCode; } private SerialPort SerialPort = null; public X10() { string portName = Spludlow.Config.Get("Spludlow.X10.PortName"); int baudRate = 4800; try { this.SerialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); this.SerialPort.Open(); this.SerialPort.ErrorReceived += SerialPort_ErrorReceived; //this.SerialPort.DataReceived += SerialPort_DataReceived; // Had trouble with the async read so did bodge int readTimeout = this.SerialPort.ReadTimeout; this.SerialPort.ReadTimeout = 1500; byte[] buffer; try { buffer = this.Read(1); } catch (TimeoutException) { buffer = null; } this.SerialPort.ReadTimeout = readTimeout; if (buffer != null && buffer[0] == 0xA5) { Spludlow.Log.Warning("X10 Setting Time 1"); this.Write(new byte[] { 0xc3 }); Spludlow.Log.Warning("X10 Setting Time 2"); this.SetTime(); Spludlow.Log.Warning("X10 Setting Time 3"); } Spludlow.Log.Report("X10 Connected: " + this.GetTime().ToString()); } catch (Exception ee) { ee = new ApplicationException("X10 Opening Serial Port: " + portName + ", " + baudRate + ", " + ee.Message, ee); throw ee; } } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (e.EventType == SerialData.Eof) return; //this.SerialPort.ReadByte } private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e) { Spludlow.Log.Error("X10 SerialPort_ErrorReceived: " + e.EventType.ToString()); } public void Stop() { this.Dispose(); Spludlow.Log.Finish("X10 Clean Exit"); } public void Dispose() { if (this.SerialPort != null) this.SerialPort.Dispose(); } public void Write(byte[] buffer) { this.SerialPort.Write(buffer, 0, buffer.Length); } public byte[] Read(int count) { byte[] buffer = new byte[count]; while (count > 0) { count -= this.SerialPort.Read(buffer, buffer.Length - count, count); } return buffer; } public DateTime GetTime() { byte[] buffer; // Request status buffer = new byte[1]; buffer[0] = 0x8B; this.Write(buffer); // Result buffer = this.Read(14); int seconds = (int)buffer[2]; // Current time(seconds) (Byte 2 ) int minuites = (int)buffer[3]; // Current time (minutes ranging from 0 to 119) (Byte 3) int hours = (int)buffer[4]; // Current time (hours/2, ranging from 0 to 11) (Byte 4) hours *= 2; if (minuites >= 60) { ++hours; minuites -= 60; } //Spludlow.Log.Info(hours + ":" + minuites + ":" + seconds); DateTime nowTime = DateTime.Now; return new DateTime(nowTime.Year, nowTime.Month, nowTime.Day, hours, minuites, seconds); } public void SetTime() { DateTime setTime = DateTime.Now; // http://www.cplusplus.com/reference/ctime/tm/ // http://www.linuxha.com/ byte[] buffer; // Time set header this.Write(new byte[] { 0x9B }); // Time info buffer = new byte[6]; int tm_sec = setTime.Second; int tm_min = setTime.Minute; int tm_hour = setTime.Hour; int tm_yday = setTime.DayOfYear - 1; // tm_yday int days since January 1 0 - 365 int tm_wday = (int)setTime.DayOfWeek; // tm_wday int days since Sunday 0 - 6 buffer[0] = (byte)tm_sec; //seconds buffer[1] = (byte)(tm_min + 60 * (tm_hour & 1)); //0-199 buffer[2] = (byte)(tm_hour >> 1); //0-11 (hours/2) buffer[3] = (byte)tm_yday; //really 9 bits buffer[4] = (byte)(1 << tm_wday); //daymask (7 bits) if ((tm_yday & 0x100) == 0x100) // buffer[4] |= 0x80; buffer[5] = 0x00; this.Write(buffer); int pcSum = this.Checksum(buffer); // X10 should now snd check sum buffer = this.Read(1); int x10Sum = (int)buffer[0]; if (x10Sum != pcSum) throw new ApplicationException("Check Sum is bad X10:" + x10Sum.ToString() + " PC:" + pcSum.ToString()); // Pc acknolage buffer = new byte[1]; buffer[0] = 0x00; this.Write(buffer); // X10 says ready buffer = this.Read(1); if (buffer[0] != 0x55) throw new ApplicationException("X10 not ready"); } public byte Checksum(byte[] buffer) { int total = 0; foreach (byte part in buffer) total += part; return (byte)total; } public static Address ParseAddress(string addressText) { if (addressText.Length < 2 || addressText.Length > 3) throw new ApplicationException("Bad X10 Address Length: " + addressText); char houseCode = Char.ToUpper(addressText[0]); if (houseCode < 'A' || houseCode > 'P') throw new ApplicationException("House Code out of range: " + houseCode.ToString()); int deviceCode = Int32.Parse(addressText.Substring(1)); if (deviceCode < 1 || deviceCode > 16) throw new ApplicationException("Device Code out of range: " + deviceCode.ToString()); Address address = new Address(); address.HouseCode = (HouseCode)Enum.Parse(typeof(HouseCode), houseCode.ToString()); address.DeviceCode = (DeviceCode)Enum.Parse(typeof(DeviceCode), "D" + deviceCode.ToString("00")); return address; } public void On(string address) { Address add = ParseAddress(address); for (int count = 0; count < 3; ++count) { this.StandardTransmission(Function.On, add.HouseCode, add.DeviceCode, false); this.StandardTransmission(Function.On, add.HouseCode, add.DeviceCode, true); Thread.Sleep(100); } } public void Off(string address) { Address add = ParseAddress(address); for (int count = 0; count < 3; ++count) { this.StandardTransmission(Function.Off, add.HouseCode, add.DeviceCode, false); this.StandardTransmission(Function.Off, add.HouseCode, add.DeviceCode, true); Thread.Sleep(100); } } public void StandardTransmission(Function function, HouseCode houseCode, DeviceCode deviceCode, bool functionOrAddress) { byte[] buffer = null; //Header:Code buffer = new byte[2]; if (functionOrAddress == true) { buffer[0] = 4 | 2; buffer[1] = (byte)(((byte)houseCode << 4) | (byte)function); } else { buffer[0] = 4 | 0; buffer[1] = (byte)(((byte)houseCode << 4) | (byte)deviceCode); } this.Write(buffer); //CheckSum byte calcCheckSum = this.Checksum(buffer); buffer = this.Read(1); if (buffer.Length != 1) throw new ApplicationException("Did not read checksum back"); if (calcCheckSum != buffer[0]) throw new ApplicationException("Bad Checksum - Controller:" + buffer[0].ToString("x") + " Calculated:" + calcCheckSum.ToString("x")); //Acknowledge buffer = new byte[1]; buffer[0] = 0x00; this.Write(buffer); //interface ready to receive buffer = this.Read(1); if (buffer.Length != 1) throw new ApplicationException("Did not read Ready to recieve back"); if (buffer[0] != 0x55) throw new ApplicationException("Controller not Ready: " + buffer[0].ToString("x")); } public static Spludlow.Io.IX10 MakeClient(string serviceAppKey) { string address = Spludlow.Config.RemotingAddress(serviceAppKey); NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); EndpointAddress endpointAddress = new EndpointAddress(address); ChannelFactory channelFactory = new ChannelFactory(binding, endpointAddress); Spludlow.Io.IX10 channel = channelFactory.CreateChannel(); return channel; } public static void OnCall(string address) { IX10 client = MakeClient("X10"); try { client.On(address); } finally { ServiceModels.Close(client); } } public static void OffCall(string address) { IX10 client = MakeClient("X10"); try { client.Off(address); } finally { ServiceModels.Close(client); } } } }