/* ==================================================================
* ModbusToggler.java - Jul 15, 2013 7:48:20 AM
*
* Copyright 2007-2013 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.node.control.modbus.toggle;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import net.solarnetwork.domain.NodeControlInfo;
import net.solarnetwork.domain.NodeControlPropertyType;
import net.solarnetwork.node.NodeControlProvider;
import net.solarnetwork.node.domain.NodeControlInfoDatum;
import net.solarnetwork.node.io.modbus.ModbusConnection;
import net.solarnetwork.node.io.modbus.ModbusConnectionAction;
import net.solarnetwork.node.io.modbus.ModbusDeviceSupport;
import net.solarnetwork.node.io.modbus.ModbusSerialConnectionFactory;
import net.solarnetwork.node.reactor.Instruction;
import net.solarnetwork.node.reactor.InstructionHandler;
import net.solarnetwork.node.reactor.InstructionStatus.InstructionState;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier;
import net.solarnetwork.node.util.ClassUtils;
import net.solarnetwork.util.OptionalService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.springframework.context.MessageSource;
/**
* Control a Modbus "coil" type register to turn a switch on or off.
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>address</dt>
* <dd>The Modbus address of the coil-type register to use.</dd>
* <dt>unitId</dt>
* <dd>The Modbus unit ID to use.</dd>
* <dt>controlId</dt>
* <dd>The {@link NodeControlProvider} UID to use.</dd>
* <dt>connectionFactory</dt>
* <dd>The {@link ModbusSerialConnectionFactory} to use.</dd>
* </dl>
*
* @author matt
* @version 1.1
*/
public class ModbusToggler extends ModbusDeviceSupport implements SettingSpecifierProvider,
NodeControlProvider, InstructionHandler {
private Integer address = 0x4008;
private String controlId = "/switch/1";
private MessageSource messageSource;
private OptionalService<EventAdmin> eventAdmin;
@Override
protected Map<String, Object> readDeviceInfo(ModbusConnection conn) {
return null;
}
/**
* Get the values of the discreet values, as a Boolean.
*
* @return Boolean for the switch status
*/
private synchronized Boolean currentValue() throws IOException {
BitSet result = performAction(new ModbusConnectionAction<BitSet>() {
@Override
public BitSet doWithConnection(ModbusConnection conn) throws IOException {
return conn.readDiscreetValues(new Integer[] { address }, 1);
}
});
if ( log.isInfoEnabled() ) {
log.info("Read {} value: {}", controlId, result.get(0));
}
return result.get(0);
}
private synchronized Boolean setValue(Boolean desiredValue) throws IOException {
final BitSet bits = new BitSet(1);
bits.set(0, desiredValue);
log.info("Setting {} value to {}", controlId, desiredValue);
final Integer[] addresses = new Integer[] { address };
return performAction(new ModbusConnectionAction<Boolean>() {
@Override
public Boolean doWithConnection(ModbusConnection conn) throws IOException {
return conn.writeDiscreetValues(addresses, bits);
}
});
}
// NodeControlProvider
@Override
public List<String> getAvailableControlIds() {
return Collections.singletonList(controlId);
}
@Override
public String getUID() {
return getControlId();
}
@Override
public NodeControlInfo getCurrentControlInfo(String controlId) {
// read the control's current status
log.debug("Reading {} status", controlId);
NodeControlInfoDatum result = null;
try {
Boolean value = currentValue();
result = newNodeControlInfoDatum(controlId, value);
} catch ( Exception e ) {
log.error("Error reading {} status: {}", controlId, e.getMessage());
}
if ( result != null ) {
postControlEvent(result, NodeControlProvider.EVENT_TOPIC_CONTROL_INFO_CAPTURED);
}
return result;
}
private NodeControlInfoDatum newNodeControlInfoDatum(String controlId, Boolean status) {
NodeControlInfoDatum info = new NodeControlInfoDatum();
info.setCreated(new Date());
info.setSourceId(controlId);
info.setType(NodeControlPropertyType.Boolean);
info.setReadonly(false);
info.setValue(status.toString());
return info;
}
private void postControlEvent(NodeControlInfo info, String topic) {
final EventAdmin admin = (eventAdmin != null ? eventAdmin.service() : null);
if ( admin == null ) {
return;
}
Map<String, Object> props = ClassUtils.getSimpleBeanProperties(info, null);
admin.postEvent(new Event(topic, props));
}
// InstructionHandler
@Override
public boolean handlesTopic(String topic) {
return InstructionHandler.TOPIC_SET_CONTROL_PARAMETER.equals(topic);
}
@Override
public InstructionState processInstruction(Instruction instruction) {
// look for a parameter name that matches a control ID
InstructionState result = null;
log.debug("Inspecting instruction {} against control {}", instruction.getId(), controlId);
for ( String paramName : instruction.getParameterNames() ) {
log.trace("Got instruction parameter {}", paramName);
if ( controlId.equals(paramName) ) {
// treat parameter value as a boolean String
String str = instruction.getParameterValue(controlId);
Boolean desiredValue = Boolean.parseBoolean(str);
Boolean modbusResult = null;
try {
modbusResult = setValue(desiredValue);
} catch ( Exception e ) {
log.warn("Error handling instruction {} on control {}: {}", instruction.getTopic(),
controlId, e.getMessage());
}
if ( modbusResult != null && modbusResult.booleanValue() ) {
postControlEvent(newNodeControlInfoDatum(controlId, desiredValue),
NodeControlProvider.EVENT_TOPIC_CONTROL_INFO_CHANGED);
result = InstructionState.Completed;
} else {
result = InstructionState.Declined;
}
}
}
return result;
}
// SettingSpecifierProvider
@Override
public String getSettingUID() {
return "net.solarnetwork.node.control.modbus.toggle";
}
@Override
public String getDisplayName() {
return "Modbus Switch Toggler";
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
ModbusToggler defaults = new ModbusToggler();
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(20);
// get current value
BasicTitleSettingSpecifier status = new BasicTitleSettingSpecifier("status", "N/A", true);
try {
Boolean val = currentValue();
status.setDefaultValue(val.toString());
} catch ( Exception e ) {
log.debug("Error reading {} status: {}", controlId, e.getMessage());
}
results.add(status);
results.add(new BasicTextFieldSettingSpecifier("controlId", defaults.controlId));
results.add(new BasicTextFieldSettingSpecifier("groupUID", defaults.getGroupUID()));
results.add(new BasicTextFieldSettingSpecifier("modbusNetwork.propertyFilters['UID']",
"Serial Port"));
results.add(new BasicTextFieldSettingSpecifier("unitId", String.valueOf(defaults.getUnitId())));
results.add(new BasicTextFieldSettingSpecifier("address", defaults.address.toString()));
return results;
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public Integer getAddress() {
return address;
}
public void setAddress(Integer address) {
this.address = address;
}
public String getControlId() {
return controlId;
}
public void setControlId(String controlId) {
this.controlId = controlId;
}
public OptionalService<EventAdmin> getEventAdmin() {
return eventAdmin;
}
public void setEventAdmin(OptionalService<EventAdmin> eventAdmin) {
this.eventAdmin = eventAdmin;
}
}