/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2004, ThoughtWorks, Inc.
* 651 W Washington Ave. Suite 600
* Chicago, IL 60661 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package net.sourceforge.cruisecontrol.publishers;
import com.jpeterson.x10.Gateway;
import com.jpeterson.x10.GatewayException;
import com.jpeterson.x10.SerialGateway;
import com.jpeterson.x10.Transmitter;
import com.jpeterson.x10.event.AddressEvent;
import com.jpeterson.x10.event.OffEvent;
import com.jpeterson.x10.event.OnEvent;
import com.jpeterson.x10.event.X10Event;
import com.jpeterson.x10.module.CM11A;
import com.jpeterson.x10.module.CM17A;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.Publisher;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import net.sourceforge.cruisecontrol.util.XMLLogHelper;
import org.apache.log4j.Logger;
import org.jdom.Element;
import java.io.IOException;
/**
* This Publisher implementation sends a on/off signal to a X10 capable device
* via the X10 Activehome computer interface, model CM11A. This allows you to
* control an electronic device when the build breaks. For example, use a
* flashing red light to indicate a broken build.
* <p/>
* NOTE: THIS PUBLISHER HAS ONLY BEEN TESTED WITH WINDOWS
* <p/>
* Quick Start:
* <ol>
* <li>Buy the home automation kit found at
* <a html="http://www.x10.com/automation/x10_ck11a_1.htm">http://www.x10.com/automation/x10_ck11a_1.htm</a></li>
* <li>Plug the computer interface to your serial port, e.g. COM1, and your powerline</li>
* <li>Set the lamp module's house and device code, e.g. A3, and plug it into your powerline</li>
* <li>Plug in an electronic device to the lamp module, e.g. a flashing red light like
* <a href="http://www.bwild.com/redsiren.html">http://www.bwild.com/redsiren.html</a></li>
* <li>Install the Java Communications API on your CruiseControl machine by copying the win32com.dll from the
* CruiseControl lib directory to your <code>JAVA_HOME/bin</code> directory</li>
* <li>Add the x10 publisher to CruiseControl's config.xml, e.g.
* <code><x10 houseCode="A" deviceCode="3" port="COM1"/></code></li>
* </ol>
* <p/>
* For more information about the controller, see
* <a html="http://www.smarthome.com/1140.html">http://www.smarthome.com/1140.html</a>
* or
* <a html="http://www.x10.com/automation/x10_ck11a_1.htm">http://www.x10.com/automation/x10_ck11a_1.htm</a>
* The controller connects to the computer via a serial port, e.g. COM1, and
* allows the computer to send (and receive) X10 signals on the power line. For
* more information on X10 in general, see
* <a href="http://www.x10.com/support/basicx10.htm">http://www.x10.com/support/basicx10.htm</a>.
* <p/>
* This module uses a pure Java implementation of the CM11A communication
* protocol as implemented by Jesse Peterson,
* <a href="http://www.jpeterson.com/">http://www.jpeterson.com/</a>. To read
* more about his library, see
* <a href="http://www.jpeterson.com/rnd/x101.0.1/Readme.html">http://www.jpeterson.com/rnd/x101.0.1/Readme.html</a>.
* <p/>
* The jpeterson library requires that the Java Communications API be installed.
* For more information on the COMM API, see
* <a href="http://java.sun.com/products/javacomm/index.jsp">http://java.sun.com/products/javacomm/index.jsp</a>.
* For convenience, the Java COMM API is included with the CruiseControl
* distribution. On windows, copy the win32com.dll from CruiseControl's lib directory to
* your <code>JAVA_HOME/bin</code> directory.
* <p/>
* NOTE: If you receive the following error:
* <PRE><CODE>Error loading win32com: java.lang.UnsatisfiedLinkError: no win32com in java.library.path</CODE></PRE>
* it probably means that the Windows DLL named win32com.dll needs to be copied
* from CruiseControl's lib directory into your JDK (or JRE) bin directory, i.e.
* the same directory that java.exe is found.
* <p/>
* The standard behavior for this publisher is to send the device the "on"
* signal when the build breaks and then the "off" signal when the build is
* successful. If you want the opposite, i.e. on when successful and off when
* broken, set the onWhenBroken attribute to false.
* <p/>
* <p/>
* Publisher Attributes:
* <ul>
* <li>houseCode - required - the house code for the device to control, A -> P case insensitive</li>
* <li>deviceCode - required - the device code the device to control, 1 -> 16</li>
* <li>port - optional - serial port to which the CM11A computer interface controller is
* connected, defaults to COM2</li>
* <li>onWhenBroken - optional - set to false if the device should turn on when the build is successful and off
* when failed, defaults to true</li>
* <li>interfaceMode - optional - set to either CM11A or CM17A depending on the model of the X10 computer interface
* being used, defaults to CM11A</li>
* </ul>
*
* @author <a href="mailto:pauljulius@users.sourceforge.net">Paul Julius</a>
* @since August 26 2004
*/
public class X10Publisher implements Publisher {
private static final Logger LOG = Logger.getLogger(X10Publisher.class);
private String houseCode;
private String deviceCode;
private String port;
private boolean onWhenBroken = true;
private String interfaceModel;
public void publish(Element cruisecontrolLog)
throws CruiseControlException {
XMLLogHelper logHelper = new XMLLogHelper(cruisecontrolLog);
handleBuild(!logHelper.isBuildSuccessful());
}
public void handleBuild(boolean isBroken) throws CruiseControlException {
if ((isBroken && onWhenBroken) || (!isBroken && !onWhenBroken)) {
turnOn();
} else {
turnOff();
}
}
public void turnOn() throws CruiseControlException {
final char houseCodeChar = houseCode.charAt(0);
final int deviceCodeInt = Integer.valueOf(deviceCode).intValue();
X10Event[] events = new X10Event[ 2 ];
events[ 0 ] = new AddressEvent(this, houseCodeChar, deviceCodeInt);
events[ 1 ] = new OnEvent(this, houseCodeChar);
send(events);
}
public void turnOff() throws CruiseControlException {
final char houseCodeChar = houseCode.charAt(0);
final int deviceCodeInt = Integer.valueOf(deviceCode).intValue();
X10Event[] events = new X10Event[ 2 ];
events[ 0 ] = new AddressEvent(this, houseCodeChar, deviceCodeInt);
events[ 1 ] = new OffEvent(this, houseCodeChar);
send(events);
}
private void send(X10Event[] events) throws CruiseControlException {
LOG.info("Sending X10 events...");
Transmitter transmitter = getTransmitter();
if (port != null) {
((SerialGateway) transmitter).setPortName(port);
}
try {
((Gateway) transmitter).allocate();
} catch (Exception e) {
throw new CruiseControlException("Trouble allocating the x10 gateway.", e);
}
for (int j = 0; j < events.length; j++) {
LOG.debug("Transmitting: " + events[ j ]);
try {
transmitter.transmit(events[ j ]);
} catch (IOException e) {
throw new CruiseControlException("Trouble transmitting event " + events[ j ], e);
}
}
if (transmitter instanceof Gateway) {
Gateway gateway = (Gateway) transmitter;
try {
LOG.debug("Wait for empty queue...");
gateway.waitGatewayState(Transmitter.QUEUE_EMPTY);
LOG.debug("Done");
} catch (InterruptedException e) {
}
LOG.debug("Deallocating...");
try {
gateway.deallocate();
} catch (GatewayException e) {
LOG.warn("Error deallocation gateway: " + e.getMessage(), e);
}
LOG.debug("Done");
}
LOG.debug("Done sending X10 events...");
}
protected Transmitter getTransmitter() throws CruiseControlException {
if (interfaceModel != null && interfaceModel.equalsIgnoreCase("CM17A")) {
return new CM17A();
} else if (interfaceModel == null || interfaceModel.equals("") || interfaceModel.equalsIgnoreCase("CM11A")) {
return new CM11A();
} else {
throw new CruiseControlException("Unknown interface model specified [" + interfaceModel + "].");
}
}
public void validate() throws CruiseControlException {
ValidationHelper.assertFalse(houseCode == null || deviceCode == null,
"Both houseCode and deviceCode are required fields.");
ValidationHelper.assertTrue(isLegalHouseCode(houseCode),
"The house code must be a single alphabetic "
+ "letter between A and P, inclusive. You specified ["
+ houseCode
+ "].");
ValidationHelper.assertTrue(isLegalDeviceCode(deviceCode),
"The device code must be an integer between"
+ " 1 and 16, inclusive. You specified ["
+ deviceCode + "]");
ValidationHelper.assertTrue(isLegalInterfaceModel(interfaceModel),
"The interface model must is not a legal value. You specified ["
+ deviceCode + "]");
}
private static boolean isLegalInterfaceModel(String model) {
return model == null
|| "".equals(model)
|| "cm11a".equalsIgnoreCase(model)
|| "cm17a".equalsIgnoreCase(model);
}
private static boolean isLegalDeviceCode(String deviceCode) {
return "1".equals(deviceCode)
|| "2".equals(deviceCode)
|| "3".equals(deviceCode)
|| "4".equals(deviceCode)
|| "5".equals(deviceCode)
|| "6".equals(deviceCode)
|| "7".equals(deviceCode)
|| "8".equals(deviceCode)
|| "9".equals(deviceCode)
|| "10".equals(deviceCode)
|| "11".equals(deviceCode)
|| "12".equals(deviceCode)
|| "13".equals(deviceCode)
|| "14".equals(deviceCode)
|| "15".equals(deviceCode)
|| "16".equals(deviceCode);
}
private static boolean isLegalHouseCode(String houseCode) {
return "A".equalsIgnoreCase(houseCode)
|| "B".equalsIgnoreCase(houseCode)
|| "C".equalsIgnoreCase(houseCode)
|| "D".equalsIgnoreCase(houseCode)
|| "E".equalsIgnoreCase(houseCode)
|| "F".equalsIgnoreCase(houseCode)
|| "G".equalsIgnoreCase(houseCode)
|| "H".equalsIgnoreCase(houseCode)
|| "I".equalsIgnoreCase(houseCode)
|| "J".equalsIgnoreCase(houseCode)
|| "K".equalsIgnoreCase(houseCode)
|| "L".equalsIgnoreCase(houseCode)
|| "M".equalsIgnoreCase(houseCode)
|| "N".equalsIgnoreCase(houseCode)
|| "O".equalsIgnoreCase(houseCode)
|| "P".equalsIgnoreCase(houseCode);
}
public void setHouseCode(String code) {
this.houseCode = code;
}
public void setDeviceCode(String code) {
this.deviceCode = code;
}
/**
* The x10 cm11a library defaults to com2 for the port, so this
* attribute is optional.
*/
public void setPort(String portName) {
this.port = portName;
}
public void setOnWhenBroken(boolean shouldTurnOn) {
this.onWhenBroken = shouldTurnOn;
}
public void setInterfaceModel(String model) {
this.interfaceModel = model;
}
}