Hello,
I guess I’m being a bit slow…
I’m attempting to do an OPC driver, Chapter 6 in the SDK manual could use some work I believe. Maybe some simpler example sources than the Modbus project as well, possibly something that actually matches the SDK documentation? I’ve also downloaded and examined the Generic TCP Driver by chi.
I attempted to use the generate stubs functionality in ModuleSDK to produce something I could build upon, but that didn’t really generate OPC Driver stubs from what I could tell.
Questions about Chapter 6:
- I don’t see how the documented DriverMeta Interface is used in either of the above examples. I do not find DriverMeta referenced anywhere in the example source code.
- I don’t see AbstractNioDriver. I do see AbstractSocketDriver, but I’m using netty for my communications layer.
- I can’t seem to get my driver to show as an available device type when trying to add a new OPC device. I’m sure I’m missing something simple.
Please ask for any clarifications. All the code I’ve copied/produced for the driver module is available at this stage. Below are the GatewayHook, Driver and DriverType sources.
[code]public class GatewayHook extends AbstractDriverModuleHook {
public static final String BUNDLE_PREFIX = “WorkshopConnection”;
public final static int DEFAULT_BROADCAST_PORT = 49152;
private static final String[] HCON_MENU_PATH = {“workshopconnection”};
private static final String INFODRIVER_MODULE_ID = “informationdriver”;
private static final List<DriverType> DRIVER_TYPES = Lists.newArrayList();
static {
DRIVER_TYPES.add(new InformationDriverType());
}
private GatewayContext context;
// private WorkshopController workshopController;
private GfmsWscSettings coreSettings;
private final Logger log = BuildLogger.getLog4JLogger(getClass());
@Override
public void setup(GatewayContext gatewayContext) {
this.context = gatewayContext;
log.debug("Beginning setup of WorkshopConnection Module " + DriverAPI.VERSION);
// Register GatewayHook.properties by registering the GatewayHook.class with BundleUtils
BundleUtil.get().addBundle(BUNDLE_PREFIX, GatewayHook.class, "WorkshopConnection");
// Disable caching in development mode - Without this, the use of bundle util
// keeps the jar file opened, even after a driver is reloaded by the dev module.
// Not a big problem, as the temp jar files won't be deleted until the JVM shuts down,
// but with this option the files can be deleted manually, if all classes are properly
// unloaded. This allows a quick test for memory leaks.
if (context.getWebApplication().getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) {
context.getWebApplication().getResourceSettings().getLocalizer().clearCache();
URLConnection con;
try {
con = new URLConnection(new URL("file://null")) {
@Override
public void connect() throws IOException {
// NOOP - This is just a dummy
}
};
// This will affect all URLConnections - not sure about side effects
con.setDefaultUseCaches(false);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
// Clear Wicket's markup cache. Actually this is only necessary when the module is upgraded to a new version, but there's
// no way to detect this situation.
if (context.getWebApplication().getMarkupSettings().getMarkupFactory().hasMarkupCache()) {
// When the gateway service is started, the is no markup cache yet.
context.getWebApplication().getResourceSettings().getPropertiesFactory().clearCache();
context.getWebApplication().getMarkupSettings().getMarkupFactory().getMarkupCache().clear();
}
//Verify tables for persistent records if necessary
verifySchema(context);
// create records if needed
maybeCreateGfmsWscSettings(context);
// get the settings record and do something with it...
coreSettings = context.getLocalPersistenceInterface().find(GfmsWscSettings.META, 0L);
log.info("Broadcast Port: " + coreSettings.getBroadcastPort());
// listen for updates to the settings record...
GfmsWscSettings.META.addRecordListener(new IRecordListener<GfmsWscSettings>() {
@Override
public void recordUpdated(GfmsWscSettings gfmsWscSettings) {
log.info("recordUpdated()");
}
@Override
public void recordAdded(GfmsWscSettings gfmsWscSettings) {
log.info("recordAdded()");
}
@Override
public void recordDeleted(KeyValue keyValue) {
log.info("recordDeleted()");
}
});
//initialize our Gateway nav menu
initMenu();
super.setup(context);
log.debug("Setup Complete.");
}
private void verifySchema(GatewayContext context) {
try {
context.getSchemaUpdater().updatePersistentRecords(GfmsWscSettings.META);
} catch (SQLException e) {
log.error("Error verifying persistent record schemas for WorkshopConnection records.", e);
}
}
public void maybeCreateGfmsWscSettings(GatewayContext context) {
log.trace("Attempting to create WorkshopConnection Settings Record");
try {
GfmsWscSettings settingsRecord = context.getLocalPersistenceInterface().createNew(GfmsWscSettings.META);
settingsRecord.setId(0L);
settingsRecord.setBroadcastPort(DEFAULT_BROADCAST_PORT);
/*
* This doesn't override existing settings, only replaces it with these if we didn't
* exist already.
*/
context.getSchemaUpdater().ensureRecordExists(settingsRecord);
} catch (Exception e) {
log.error("Failed to establish GfmsWscSettings Record exists", e);
}
log.trace("WorkshopConnection Settings Record Established");
}
private void initMenu() {
log.info("InitMenu Called");
/* header is the top-level title in the gateway config page, e.g. System, Configuration, etc */
LabelConfigMenuNode header = new LabelConfigMenuNode(HCON_MENU_PATH[0], "WorkshopConnection.nav.header");
header.setPosition(801);
context.getConfigMenuModel().addConfigMenuNode(null, header);
/* Create the nodes/links that will exist under our parent nav header */
LinkConfigMenuNode settingsNode = new LinkConfigMenuNode("settings",
"WorkshopConnection.nav.settings.title",
GfmsWscSettingsPage.class);
/* register our nodes with the context config menu model */
context.getConfigMenuModel().addConfigMenuNode(HCON_MENU_PATH, settingsNode);
}
@Override
public void startup(LicenseState licenseState) {
log.info("Startup Called");
// if (workshopController == null) {
// workshopController = new WorkshopController();
// }
if (coreSettings == null) {
coreSettings = context.getLocalPersistenceInterface().find(GfmsWscSettings.META, 0L);
}
// workshopController.startInfoBroadcastListener(coreSettings.getBroadcastPort());
super.startup(licenseState);
// context.getModuleServicesManager().subscribe(LegacyDeviceManager.class, this);
}
@Override
public void shutdown() {
log.info("Shutdown Called");
/* shutdown I/O */
// if (workshopController != null) {
// workshopController.shutdownWorkshop();
// }
/* remove our bundle */
BundleUtil.get().removeBundle("WorkshopConnection");
/* remove our nodes from the menu */
context.getConfigMenuModel().removeConfigMenuNode(HCON_MENU_PATH);
ResourceBundle.clearCache();
super.shutdown();
}
@Override
public void serviceReady(Class<?> serviceClass) {
log.info("ServiceReady Called for " + serviceClass.toString());
// if (serviceClass == LegacyDeviceManager.class) {
// driverManager = (LegacyDeviceManagerDevice)context.getModuleServicesManager().getService(LegacyDeviceManager.class);
// }
}
@Override
protected List<DriverType> getDriverTypes() {
log.info("GetDriverTypes Called");
return DRIVER_TYPES;
}
@Override
protected int getExpectedAPIVersion() {
log.info("GetExpectedAPIVersion Called");
return 4;
}
}
[/code]
[code]public class InformationDriver extends AbstractDriver {
public final static int DEFAULT_BROADCAST_PORT = 49152;
private InformationDriverSettings driverSettings;
private WorkshopController workshopController;
private final List<Node> uaNodes = new ArrayList<Node>();
private final Map<String, BrowseNode> nodeMap = new HashMap<String, BrowseNode>();
private final Map<String, String> mappedAddresses = new HashMap<String, String>();
private static final String ROOT_NODE_ADDRESS = "ROOT";
private final FolderNode rootNode = new FolderNode(ROOT_NODE_ADDRESS);
private final Logger log = BuildLogger.getLog4JLogger(getClass());
public InformationDriver(DriverContext driverContext, InformationDriverSettings driverSettings) {
super(driverContext);
this.driverSettings = driverSettings;
Integer port = driverSettings.getBroadcastPort();
log.info("Adding Diagnostinc Tag");
addDriverTag(new StaticDriverTag("[Diagnostics]/Port",
DataType.UInt16,
new DataValue(new Variant(new UInt16(port)))));
}
@Override
protected void connect() {
log.info("Connect Called");
if (workshopController == null) {
workshopController = new WorkshopController();
}
log.info("Broadcast Port: " + driverSettings.getBroadcastPort());
log.info("Starting Broadcast");
workshopController.startInfoBroadcastListener(driverSettings.getBroadcastPort());
}
@Override
protected void disconnect() {
log.info("Disconnect Called");
/* shutdown I/O */
if (workshopController != null) {
log.info("Stopping Broadcast");
workshopController.shutdownWorkshop();
}
}
@Override
protected Request createBrowseRequest(BrowseOperation browseOp) {
List<String> results = new ArrayList<String>();
log.info("Creating Browse Request");
String address = browseOp.getStartingAddress();
if (address == null || address.isEmpty()) {
address = ROOT_NODE_ADDRESS;
}
BrowseNode node = nodeMap.get(address);
if (node != null) {
Iterator<BrowseNode> iter = node.getChildren();
while (iter.hasNext()) {
results.add(iter.next().getAddress());
}
}
browseOp.browseDone(StatusCode.GOOD, results, currentGuid());
return null;
}
@Override
protected Object getRequestKey(Object o) {
log.info("Getting Request Key");
return null;
}
@Override
protected boolean isBrowsingSupported() {
return true;
}
@Override
protected boolean isOfflineBrowsingSupported() {
return true;
}
@Override
protected Request createWriteRequest(List list) {
log.info("Creating Write Request");
return null;
}
@Override
protected Request createReadRequest(List list) {
log.info("Creating Read Request");
return null;
}
@Override
protected List<List<? extends ReadItem>> optimizeRead(List list) {
log.info("Optimized Read");
return null;
}
@Override
public void buildNode(String s, NodeId nodeId) throws AddressNotFoundException {
log.info("Building Node");
DataType dataType = DataType.String;
BrowseNode node = nodeMap.get(s);
if (node == null) {
node = new DataVariableNode(
s,
s,
new DataValue(StatusCode.BAD),
dataType);
nodeMap.put(s, node);
}
if (node instanceof FolderNode) {
Node uaNode = builderFactory.newObjectNodeBuilder()
.setNodeId(nodeId)
.setBrowseName(new QualifiedName(1, node.getDisplayName()))
.setDisplayName(new LocalizedText(node.getDisplayName()))
.setTypeDefinition(NodeIds.FolderType_ObjectType.getNodeId())
.buildAndAdd(nodeManager);
uaNodes.add(uaNode);
return;
} else if (node instanceof DataVariableNode) {
Node uaNode = builderFactory.newVariableNodeBuilder()
.setNodeId(nodeId)
.setBrowseName(new QualifiedName(1, node.getDisplayName()))
.setDisplayName(new LocalizedText(node.getDisplayName()))
.setDataType(dataType.getNodeId())
.setTypeDefinition(NodeIds.VariableNode_DataType.getNodeId())
.setAccessLevel(getAccessLevel(""))
.setUserAccessLevel(getAccessLevel(""))
.buildAndAdd(nodeManager);
uaNodes.add(uaNode);
} else {
throw new AddressNotFoundException(String.format("Address \"%s\" not found.", s));
}
}
private EnumSet<AccessLevel> getAccessLevel(String address) {
log.info("Getting Access Level");
EnumSet<AccessLevel> accessLevel = EnumSet.of(AccessLevel.CurrentRead);
// ModbusTable table = address.getTable();
// if (table == ModbusTable.HoldingRegisters || table == ModbusTable.Coils) {
accessLevel.add(AccessLevel.CurrentWrite);
// }
return accessLevel;
}
}
[/code]
[code]public class InformationDriverType extends DriverType {
public static final String TYPE_ID = "Information";
private final Logger log = BuildLogger.getLog4JLogger(getClass());
public InformationDriverType() {
super(TYPE_ID, "WorkshopConnection." + "InformationDriverType.Name", "WorkshopConnection." + "InformationDriverType.Description");
}
@Override
public RecordMeta<? extends PersistentRecord> getSettingsRecordType() {
log.info("GetSettingsRecordType Called");
return InformationDriverSettings.META;
}
@Override
public ReferenceField<?> getSettingsRecordForeignKey() {
log.info("GetSettingsRecordForeignKey Called");
return InformationDriverSettings.DeviceSettings;
}
@Override
public List<LinkEntry> getLinks() {
log.info("GetLinks Called");
return Lists.newArrayList(new DiagnosticsLink());
}
@Override
public Driver createDriver(DriverContext driverContext, DeviceSettingsRecord deviceSettings) {
log.info("CreateDriver Called");
InformationDriverSettings settings = findProfileSettingsRecord(driverContext.getGatewayContext(), deviceSettings);
log.info("Settings " + settings.toString());
return new InformationDriver(driverContext, settings);
}
}
[/code]