Are there any plans to support BBMD in BACnet4J?
Thanks.
Are there any plans to support BBMD in BACnet4J?
Thanks.
Are there any plans to support BBMD in BACnet4J?
Thanks.
Matthew,
I'm getting an exception in seroUtils.jar. Near as I can tell it occurs when there are multiple concurrent requests for values for several different slaves. The exception stack is:
java.io.IOException: Stream closed.
at java.net.PlainSocketImpl.available(PlainSocketImpl.java:434)
at java.net.SocketInputStream.available(SocketInputStream.java:217)
at java.io.BufferedInputStream.available(BufferedInputStream.java:381)
at com.serotonin.io.InputStreamListener.run(InputStreamListener.java:67)
The only way it appears I can correct the problem is to restart the application and everything will work fine so long as I don't hammer the requests concurrently.
Have you seen this before?
The version of Modbus4J is 1.02.
Regards,
Fred
Matthew,
I'm getting an exception in seroUtils.jar. Near as I can tell it occurs when there are multiple concurrent requests for values for several different slaves. The exception stack is:
java.io.IOException: Stream closed.
at java.net.PlainSocketImpl.available(PlainSocketImpl.java:434)
at java.net.SocketInputStream.available(SocketInputStream.java:217)
at java.io.BufferedInputStream.available(BufferedInputStream.java:381)
at com.serotonin.io.InputStreamListener.run(InputStreamListener.java:67)
The only way it appears I can correct the problem is to restart the application and everything will work fine so long as I don't hammer the requests concurrently.
Have you seen this before?
The version of Modbus4J is 1.02.
Regards,
Fred
Matthew,
Can you tell me what prompted you to change the licensing for Bacnet4J and Modbus4J from LGPL to GPL v3?
Is there another way these products can be licensed other than GPL v3?
Regards,
Fred
Matthew,
Can you tell me what prompted you to change the licensing for Bacnet4J and Modbus4J from LGPL to GPL v3?
Is there another way these products can be licensed other than GPL v3?
Regards,
Fred
Thanks Matthew,
I did the build myself for the update you checked in for SequenceOf.java since I didn't see an updated jar file. The rest of my code is relative to the latest release published on sourceforge.
If I can avoid it, I never like building 3rd party libraries.
Regards,
Fred
Thanks Matthew, that did the trick.
Can you share your plans on when you'll be updating the released versions of bacnet4j and modbus4j?
Regards.
Matthew,
I've been doing a bit of testing and I hit a NPE in SequenceOf.java.
Here's a little background:
I give my user the ability to add/remove sensors (analog or binary inputs) they want exposed through BACnet. So let's say I create 10 bacnet objects I add to my local device. Everything is fine. I hit my slave server with a BACnet browser and I can see everything as expected. If I delete any number of bacnet objects using removeObject on the local device and then run the scan again from my BACnet browser, I get the exception.
Here is the full stack trace:
java.lang.NullPointerException
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.constructed.BaseType.write(BaseType.java:33)
at com.serotonin.bacnet4j.type.Encodable.writeEncodable(Encodable.java:272)
at com.serotonin.bacnet4j.type.constructed.ReadAccessResult$Result.write(ReadAccessResult.java:114)
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.constructed.BaseType.write(BaseType.java:33)
at com.serotonin.bacnet4j.type.Encodable.write(Encodable.java:191)
at com.serotonin.bacnet4j.type.Encodable.writeOptional(Encodable.java:205)
at com.serotonin.bacnet4j.type.constructed.ReadAccessResult.write(ReadAccessResult.java:45)
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.Encodable.write(Encodable.java:162)
at com.serotonin.bacnet4j.service.acknowledgement.ReadPropertyMultipleAck.write(ReadPropertyMultipleAck.java:46)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl.sendResponse(IpMessageControl.java:239)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl.access$300(IpMessageControl.java:74)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl$IncomingMessageExecutor.runImpl(IpMessageControl.java:536)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl$IncomingMessageExecutor.run(IpMessageControl.java:454)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Regards,
Fred
Matthew,
I've been doing a bit of testing and I hit a NPE in SequenceOf.java.
Here's a little background:
I give my user the ability to add/remove sensors (analog or binary inputs) they want exposed through BACnet. So let's say I create 10 bacnet objects I add to my local device. Everything is fine. I hit my slave server with a BACnet browser and I can see everything as expected. If I delete any number of bacnet objects using removeObject on the local device and then run the scan again from my BACnet browser, I get the exception.
Here is the full stack trace:
java.lang.NullPointerException
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.constructed.BaseType.write(BaseType.java:33)
at com.serotonin.bacnet4j.type.Encodable.writeEncodable(Encodable.java:272)
at com.serotonin.bacnet4j.type.constructed.ReadAccessResult$Result.write(ReadAccessResult.java:114)
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.constructed.BaseType.write(BaseType.java:33)
at com.serotonin.bacnet4j.type.Encodable.write(Encodable.java:191)
at com.serotonin.bacnet4j.type.Encodable.writeOptional(Encodable.java:205)
at com.serotonin.bacnet4j.type.constructed.ReadAccessResult.write(ReadAccessResult.java:45)
at com.serotonin.bacnet4j.type.constructed.SequenceOf.write(SequenceOf.java:49)
at com.serotonin.bacnet4j.type.Encodable.write(Encodable.java:162)
at com.serotonin.bacnet4j.service.acknowledgement.ReadPropertyMultipleAck.write(ReadPropertyMultipleAck.java:46)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl.sendResponse(IpMessageControl.java:239)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl.access$300(IpMessageControl.java:74)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl$IncomingMessageExecutor.runImpl(IpMessageControl.java:536)
at com.serotonin.bacnet4j.npdu.ip.IpMessageControl$IncomingMessageExecutor.run(IpMessageControl.java:454)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Regards,
Fred
Ah, I got it. Similar to the quality property in OPC.
Thanks.
Matthew,
I'm trying to publish sensor values via BACnet. There are occasions when I cannot contact the sensor and I want to remove the current value for the bacnet object.
I had tried to remove the present value property of the bacnet object and an exception was promptly thrown. I traced this back to the fact that the present value property is required. Ok, so I looked at the other primitive data types and saw Null. So my next thought was to set the present value to a Null value however the BACnet code doesn't like that for analog inputs.
Is there a concept of an input not having a present value in BACnet?
As always, thanks for the help.
Fred
Matthew,
I'm trying to publish sensor values via BACnet. There are occasions when I cannot contact the sensor and I want to remove the current value for the bacnet object.
I had tried to remove the present value property of the bacnet object and an exception was promptly thrown. I traced this back to the fact that the present value property is required. Ok, so I looked at the other primitive data types and saw Null. So my next thought was to set the present value to a Null value however the BACnet code doesn't like that for analog inputs.
Is there a concept of an input not having a present value in BACnet?
As always, thanks for the help.
Fred
Ok, I was looking for support pretty much from the slave side only. I assume that would just be to relax the offset validation in the ProcessImage class. I'll try that and see what happens.
Thanks.
I'll defer to your expertise in this area as I am absolutely not an authority on Modbus.
I had asked because I had seen numerous references to products accessing holding registers in the 400000-499999 range. I assumed this was due to an evolution in the protocol.
I did find this doc on the web that shed some additional light:
Thanks.
Matthew,
Do you know how big a deal it would be for Modbus4J to support both 5 digit and 6 digit addressing?
Regards,
Fred
Matthew,
Do you know how big a deal it would be for Modbus4J to support both 5 digit and 6 digit addressing?
Regards,
Fred
Matthew,
That should work. I think you're right in that you might have some class name confusion as people may view the slave at the device id level instead of the networking ip/port level as the new semantics are.
Not that I think it would be a big performance hit, but with the process images backed by a hashmap keyed by slave id, you don't have to do a linear search to find the requested process image.
I may keep my current implementation simply because my process image registry is a collection of process images keyed first by slave id and secondly by my concept of a slave configuration (allowing for more than one process image if a "spill over" process image is needed). This class is thread safe and handles the addition/removal of process images by my slave objects. That said, I think this logic could easily be moved into the ModbusSlave class where if necessary I can create my own implementation.
So the answer to your question is yes with the reservation on the class naming as you pointed out.
Regards,
Fred
Matthew,
I did get around to it and it appears to be working.
At the heart of it, I decoupled the ProcessImage object from the TcpSlave. I ended up creating my own custom IpRequestHandler named appropriately CustomIpRequestHandler. The code for it is:
public class CustomIpRequestHandler extends IpRequestHandler {
private ProcessImageRegistry _processImageRegistry;
public CustomIpRequestHandler(ProcessImageRegistry processImageRegistry) {
super(0, null);
_processImageRegistry = processImageRegistry;
}
public MessageResponse handleRequest(MessageRequest req) throws Exception {
CustomIpMessageRequest tcpRequest = (CustomIpMessageRequest)req;
ModbusRequest request = tcpRequest.getModbusRequest();
ProcessImage processImage = _processImageRegistry.getProcessImage(request.getSlaveId());
if (processImage == null) {
return null;
}
ModbusResponse response = request.handle(processImage);
return new IpMessageResponse(response, tcpRequest.getTransactionId());
}
}
You've obviously noticed it called the superclass constructor with bogus parameters. My subclass takes a reference to a new object which I called a "ProcessImageRegistry". This class is basically backed by a concurrent hashmap that is keyed by slave id. There is more to my ProcessImageRegistry than what I've described here that is specific to my application.
So then I ripped off your TcpConnectionHandler which was specific to a TcpSlave but now deals with the ProcessImageRegistry.
class TcpConnectionHandler implements Runnable {
private final Socket _socket;
private TestableTransport _transport;
private ListenerConnection _conn;
private ProcessImageRegistry _processImageRegistry;
private MessagingConnectionListener _exceptionListener = new DefaultExceptionListener();
TcpConnectionHandler(Socket socket, ProcessImageRegistry processImageRegistry) throws ModbusInitException {
_socket = socket;
_processImageRegistry = processImageRegistry;
try {
_transport = new TestableTransport(_socket.getInputStream(), _socket.getOutputStream(), "Modbus4J TcpSlave");
}
catch (IOException e) {
throw new ModbusInitException(e);
}
}
public void run() {
CustomIpMessageParser ipMessageParser = new CustomIpMessageParser();
CustomIpRequestHandler ipRequestHandler = new CustomIpRequestHandler(_processImageRegistry);
_conn = new ListenerConnection(ipRequestHandler);
_conn.addListener(_exceptionListener);
try {
_conn.start(_transport, ipMessageParser);
}
catch (IOException e) {
_exceptionListener.receivedException(new ModbusInitException(e));
}
// Monitor the socket to detect when it gets closed.
while (true) {
try {
_transport.testInputStream();
}
catch (IOException e) {
break;
}
try {
Thread.sleep(500);
}
catch (InterruptedException e) {}
}
_conn.close();
try {
_socket.close();
}
catch (IOException e) {
_exceptionListener.receivedException(new ModbusInitException(e));
}
}
}
I ended up having to create a CustomIpMessageParser:
public class CustomIpMessageParser extends IpMessageParser {
protected MessageResponse parseResponseImpl(ByteQueue queue) throws Exception {
return CustomIpMessageResponse.createIpMessageResponse(queue);
}
protected MessageRequest parseRequestImpl(ByteQueue queue) throws Exception {
return CustomIpMessageRequest.createIpMessageRequest(queue);
}
}
The reason I need to do this what to create a CustomIpMessageRequest and a CustomIpMessageResponse. This was because I needed the transactionId in both the IpMessageRequest and IpMessageResponse but that field was private to both of those classes.
My custom subclasses simply provided a getter for the transactionId.
public class CustomIpMessageRequest extends IpMessageRequest {
private int _transactionId;
static CustomIpMessageRequest createIpMessageRequest(ByteQueue queue) throws ModbusTransportException {
// Remove the IP header
int transactionId = ModbusUtils.popShort(queue);
int protocolId = ModbusUtils.popShort(queue);
if (protocolId != ModbusUtils.IP_PROTOCOL_ID) {
throw new ModbusTransportException("Unsupported IP protocol id: "+ protocolId);
}
ModbusUtils.popShort(queue); // Length, which we don't care about.
// Create the modbus response.
ModbusRequest request = ModbusRequest.createModbusRequest(queue);
return new CustomIpMessageRequest(request, transactionId);
}
public CustomIpMessageRequest(ModbusRequest modbusRequest, int transactionId) {
super(modbusRequest, transactionId);
_transactionId = transactionId;
}
public void isValidResponse(MessageResponse res) throws MessageMismatchException {
if (!(res instanceof CustomIpMessageResponse)) {
throw new MessageMismatchException("Response is not an CustomIpMessageResponse: "+ res.getClass());
}
CustomIpMessageResponse response = (CustomIpMessageResponse)res;
if (_transactionId != response.getTransactionId()) {
throw new MessageMismatchException("Response transaction id does not match: expected="+ _transactionId + ", received="+ response.getTransactionId());
}
getModbusRequest().matches(response.getModbusResponse());
}
public int getTransactionId() {
return _transactionId;
}
}
public class CustomIpMessageResponse extends IpMessageResponse {
private int _transactionId;
static CustomIpMessageResponse createIpMessageResponse(ByteQueue queue) throws ModbusTransportException {
// Remove the IP header
int transactionId = ModbusUtils.popShort(queue);
int protocolId = ModbusUtils.popShort(queue);
if (protocolId != ModbusUtils.IP_PROTOCOL_ID) {
throw new ModbusTransportException("Unsupported IP protocol id: "+ protocolId);
}
ModbusUtils.popShort(queue); // Length, which we don't care about.
// Create the modbus response.
ModbusResponse response = ModbusResponse.createModbusResponse(queue);
return new CustomIpMessageResponse(response, transactionId);
}
public CustomIpMessageResponse(ModbusResponse modbusResponse, int transactionId) {
super(modbusResponse, transactionId);
_transactionId = transactionId;
}
public int getTransactionId() {
return _transactionId;
}
}
My data model allows for the idea of one or more "spill over" slaves where if the address range for one slave is insufficient to publish out everything the customer wants to see on mobus, they can configure additional slaves to handle a population larger than say 5000 float values (analog inputs) on a single slave.
I hope this helps and if you want some clarification, I'd be happy to elaborate further.
Hey, I did want to ask you a question though. The current modbus4j uses 5 digit register offsets. Is that because 5 digit is the most common? Would supporting 6 digit register offsets be a major issue?
Regards,
Fred
Matthew,
Thanks again. I can add that to my slave configuration as an option.
At the risk of going to the well too often - is the default for Modbus slaves little endian? Do slaves typically allow this to be configured?
Regards,
Fred