mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[ipcamera] FIX: TAPO branded cameras require xAddr port to be different (#15073)
* FIX TAPO branded cameras require xAddr port to be different from the main ONVIF PORT * Fix for old API instar cameras. --------- Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
parent
6e85021f6b
commit
8701b86a37
@ -176,6 +176,8 @@ public class Ffmpeg {
|
||||
case SNAPSHOT:
|
||||
notFrozen = true;// RTSP_ALARMS, MJPEG and SNAPSHOT all set this to true, no break.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +384,9 @@ public class InstarHandler extends ChannelDuplexHandler {
|
||||
ArrayList<String> lowPriorityRequests = new ArrayList<String>(7);
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getaudioalarmattr");
|
||||
lowPriorityRequests.add("/cgi-bin/hi3510/param.cgi?cmd=getmdattr");
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getalarmattr");
|
||||
if (ipCameraHandler.newInstarApi) {// old API cameras get a error 404 response to this
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getalarmattr");
|
||||
}
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getinfrared");
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getoverlayattr&-region=1");
|
||||
lowPriorityRequests.add("/param.cgi?cmd=getpirattr");
|
||||
|
@ -364,7 +364,6 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public void userEventTriggered(@Nullable ChannelHandlerContext ctx, @Nullable Object evt) throws Exception {
|
||||
if (ctx == null) {
|
||||
return;
|
||||
@ -384,7 +383,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
}
|
||||
ChannelTracking channelTracking = channelTrackingMap.get(urlToKeepOpen);
|
||||
if (channelTracking != null) {
|
||||
if (channelTracking.getChannel() == ctx.channel()) {
|
||||
if (channelTracking.getChannel().equals(ctx.channel())) {
|
||||
return; // don't auto close this as it is for the alarms.
|
||||
}
|
||||
}
|
||||
@ -745,7 +744,6 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
* open large amounts of channels. This may help to keep it under control and WARN the user every 8 seconds this is
|
||||
* still occurring.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
private void cleanChannels() {
|
||||
for (Channel channel : openChannels) {
|
||||
boolean oldChannel = true;
|
||||
@ -753,7 +751,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
if (!channelTracking.getChannel().isOpen() && channelTracking.getReply().isEmpty()) {
|
||||
channelTrackingMap.remove(channelTracking.getRequestUrl());
|
||||
}
|
||||
if (channelTracking.getChannel() == channel) {
|
||||
if (channelTracking.getChannel().equals(channel)) {
|
||||
logger.debug("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl());
|
||||
oldChannel = false;
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
|
||||
@ -308,7 +307,7 @@ public class OnvifConnection {
|
||||
if (message.contains("PullMessagesResponse")) {
|
||||
eventRecieved(message);
|
||||
} else if (message.contains("RenewResponse")) {
|
||||
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
|
||||
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
|
||||
} else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
|
||||
connecting.lock();
|
||||
try {
|
||||
@ -320,7 +319,7 @@ public class OnvifConnection {
|
||||
logger.debug("Openhabs UTC dateTime is:{}", getUTCdateTime());
|
||||
} else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
|
||||
parseXAddr(message);
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetProfiles, mediaXAddr));
|
||||
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
|
||||
} else if (message.contains("GetProfilesResponse")) {// 3rd to be sent.
|
||||
connecting.lock();
|
||||
try {
|
||||
@ -329,25 +328,25 @@ public class OnvifConnection {
|
||||
connecting.unlock();
|
||||
}
|
||||
parseProfiles(message);
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetSnapshotUri, mediaXAddr));
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetStreamUri, mediaXAddr));
|
||||
sendOnvifRequest(RequestType.GetSnapshotUri, mediaXAddr);
|
||||
sendOnvifRequest(RequestType.GetStreamUri, mediaXAddr);
|
||||
if (ptzDevice) {
|
||||
sendPTZRequest(RequestType.GetNodes);
|
||||
}
|
||||
if (usingEvents) {// stops API cameras from getting sent ONVIF events.
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetEventProperties, eventXAddr));
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetServiceCapabilities, eventXAddr));
|
||||
sendOnvifRequest(RequestType.GetEventProperties, eventXAddr);
|
||||
sendOnvifRequest(RequestType.GetServiceCapabilities, eventXAddr);
|
||||
}
|
||||
} else if (message.contains("GetServiceCapabilitiesResponse")) {
|
||||
if (message.contains("WSSubscriptionPolicySupport=\"true\"")) {
|
||||
sendOnvifRequest(requestBuilder(RequestType.Subscribe, eventXAddr));
|
||||
sendOnvifRequest(RequestType.Subscribe, eventXAddr);
|
||||
}
|
||||
} else if (message.contains("GetEventPropertiesResponse")) {
|
||||
sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
|
||||
sendOnvifRequest(RequestType.CreatePullPointSubscription, eventXAddr);
|
||||
} else if (message.contains("CreatePullPointSubscriptionResponse")) {
|
||||
subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
|
||||
logger.debug("subscriptionXAddr={}", subscriptionXAddr);
|
||||
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
|
||||
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
|
||||
} else if (message.contains("GetStatusResponse")) {
|
||||
processPTZLocation(message);
|
||||
} else if (message.contains("GetPresetsResponse")) {
|
||||
@ -380,54 +379,6 @@ public class OnvifConnection {
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequest requestBuilder(RequestType requestType, String xAddr) {
|
||||
logger.trace("Sending ONVIF request:{}", requestType);
|
||||
String security = "";
|
||||
String extraEnvelope = "";
|
||||
String headerTo = "";
|
||||
String getXmlCache = getXml(requestType);
|
||||
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
||||
|| requestType.equals(RequestType.Renew) || requestType.equals(RequestType.Unsubscribe)) {
|
||||
headerTo = "<a:To s:mustUnderstand=\"1\">" + xAddr + "</a:To>";
|
||||
extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
||||
}
|
||||
String headers;
|
||||
if (!password.isEmpty() && !requestType.equals(RequestType.GetSystemDateAndTime)) {
|
||||
String nonce = createNonce();
|
||||
String dateTime = getUTCdateTime();
|
||||
String digest = createDigest(nonce, dateTime);
|
||||
security = "<Security s:mustUnderstand=\"1\" xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><UsernameToken><Username>"
|
||||
+ user
|
||||
+ "</Username><Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"
|
||||
+ digest
|
||||
+ "</Password><Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">"
|
||||
+ encodeBase64(nonce)
|
||||
+ "</Nonce><Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
|
||||
+ dateTime + "</Created></UsernameToken></Security>";
|
||||
headers = "<s:Header>" + security + headerTo + "</s:Header>";
|
||||
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
||||
headers = "";
|
||||
}
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"),
|
||||
removeIPfromUrl(xAddr));
|
||||
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
||||
request.headers().add("Content-Type",
|
||||
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
||||
request.headers().add("Charset", "utf-8");
|
||||
request.headers().set("Host", ipAddress + ":" + onvifPort);
|
||||
request.headers().set("Connection", HttpHeaderValues.CLOSE);
|
||||
request.headers().set("Accept-Encoding", "gzip, deflate");
|
||||
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
|
||||
+ headers
|
||||
+ "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
|
||||
+ getXmlCache + "</s:Body></s:Envelope>";
|
||||
request.headers().add("SOAPAction", "\"" + actionString + "/" + requestType + "\"");
|
||||
ByteBuf bbuf = Unpooled.copiedBuffer(fullXml, StandardCharsets.UTF_8);
|
||||
request.headers().set("Content-Length", bbuf.readableBytes());
|
||||
request.content().clear().writeBytes(bbuf);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link removeIPfromUrl} Will throw away all text before the cameras IP, also removes the IP and the PORT
|
||||
* leaving just the URL.
|
||||
@ -456,6 +407,19 @@ public class OnvifConnection {
|
||||
return "";
|
||||
}
|
||||
|
||||
int extractPortFromUrl(String url) {
|
||||
int startIndex = url.indexOf("//") + 2;// skip past http://
|
||||
startIndex = url.indexOf(":", startIndex);
|
||||
if (startIndex == -1) {// no port defined so use port 80
|
||||
return 80;
|
||||
}
|
||||
int endIndex = url.indexOf("/", startIndex);// skip past any :port to the slash /
|
||||
if (endIndex == -1) {
|
||||
return 80;
|
||||
}
|
||||
return Integer.parseInt(url.substring(startIndex + 1, endIndex));
|
||||
}
|
||||
|
||||
void parseXAddr(String message) {
|
||||
// Normally I would search '<tt:XAddr>' instead but Foscam needed this work around.
|
||||
String temp = Helper.fetchXML(message, "<tt:Device", "tt:XAddr");
|
||||
@ -536,19 +500,64 @@ public class OnvifConnection {
|
||||
return Base64.getEncoder().encodeToString(encryptedRaw);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void sendOnvifRequest(HttpRequest request) {
|
||||
if (bootstrap == null) {
|
||||
public void sendOnvifRequest(RequestType requestType, String xAddr) {
|
||||
logger.trace("Sending ONVIF request:{}", requestType);
|
||||
String security = "";
|
||||
String extraEnvelope = "";
|
||||
String headerTo = "";
|
||||
String getXmlCache = getXml(requestType);
|
||||
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
||||
|| requestType.equals(RequestType.Renew) || requestType.equals(RequestType.Unsubscribe)) {
|
||||
headerTo = "<a:To s:mustUnderstand=\"1\">" + xAddr + "</a:To>";
|
||||
extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
||||
}
|
||||
String headers;
|
||||
if (!password.isEmpty() && !requestType.equals(RequestType.GetSystemDateAndTime)) {
|
||||
String nonce = createNonce();
|
||||
String dateTime = getUTCdateTime();
|
||||
String digest = createDigest(nonce, dateTime);
|
||||
security = "<Security s:mustUnderstand=\"1\" xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><UsernameToken><Username>"
|
||||
+ user
|
||||
+ "</Username><Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"
|
||||
+ digest
|
||||
+ "</Password><Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">"
|
||||
+ encodeBase64(nonce)
|
||||
+ "</Nonce><Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
|
||||
+ dateTime + "</Created></UsernameToken></Security>";
|
||||
headers = "<s:Header>" + security + headerTo + "</s:Header>";
|
||||
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
||||
headers = "";
|
||||
}
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"),
|
||||
removeIPfromUrl(xAddr));
|
||||
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
||||
request.headers().add("Content-Type",
|
||||
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
||||
request.headers().add("Charset", "utf-8");
|
||||
request.headers().set("Host", ipAddress + ":" + onvifPort);
|
||||
request.headers().set("Connection", HttpHeaderValues.CLOSE);
|
||||
request.headers().set("Accept-Encoding", "gzip, deflate");
|
||||
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
|
||||
+ headers
|
||||
+ "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
|
||||
+ getXmlCache + "</s:Body></s:Envelope>";
|
||||
request.headers().add("SOAPAction", "\"" + actionString + "/" + requestType + "\"");
|
||||
ByteBuf bbuf = Unpooled.copiedBuffer(fullXml, StandardCharsets.UTF_8);
|
||||
request.headers().set("Content-Length", bbuf.readableBytes());
|
||||
request.content().clear().writeBytes(bbuf);
|
||||
|
||||
Bootstrap localBootstap = bootstrap;
|
||||
if (localBootstap == null) {
|
||||
mainEventLoopGroup = new NioEventLoopGroup(2);
|
||||
bootstrap = new Bootstrap();
|
||||
bootstrap.group(mainEventLoopGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
|
||||
bootstrap.option(ChannelOption.SO_SNDBUF, 1024 * 8);
|
||||
bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
|
||||
bootstrap.option(ChannelOption.TCP_NODELAY, true);
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
localBootstap = new Bootstrap();
|
||||
localBootstap.group(mainEventLoopGroup);
|
||||
localBootstap.channel(NioSocketChannel.class);
|
||||
localBootstap.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
localBootstap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
|
||||
localBootstap.option(ChannelOption.SO_SNDBUF, 1024 * 8);
|
||||
localBootstap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
|
||||
localBootstap.option(ChannelOption.TCP_NODELAY, true);
|
||||
localBootstap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel socketChannel) throws Exception {
|
||||
@ -557,26 +566,28 @@ public class OnvifConnection {
|
||||
socketChannel.pipeline().addLast("OnvifCodec", new OnvifCodec(getHandle()));
|
||||
}
|
||||
});
|
||||
bootstrap = localBootstap;
|
||||
}
|
||||
if (!mainEventLoopGroup.isShuttingDown()) {
|
||||
bootstrap.connect(new InetSocketAddress(ipAddress, onvifPort)).addListener(new ChannelFutureListener() {
|
||||
localBootstap.connect(new InetSocketAddress(ipAddress, extractPortFromUrl(xAddr)))
|
||||
.addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(@Nullable ChannelFuture future) {
|
||||
if (future == null) {
|
||||
return;
|
||||
}
|
||||
if (future.isSuccess()) {
|
||||
Channel ch = future.channel();
|
||||
ch.writeAndFlush(request);
|
||||
} else { // an error occured
|
||||
logger.debug("Camera is not reachable on ONVIF port:{} or the port may be wrong.", onvifPort);
|
||||
if (isConnected) {
|
||||
disconnect();
|
||||
@Override
|
||||
public void operationComplete(@Nullable ChannelFuture future) {
|
||||
if (future == null) {
|
||||
return;
|
||||
}
|
||||
if (future.isSuccess()) {
|
||||
Channel ch = future.channel();
|
||||
ch.writeAndFlush(request);
|
||||
} else { // an error occured
|
||||
logger.debug("Camera is not reachable when using xAddr:{}.", xAddr);
|
||||
if (isConnected) {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.debug("ONVIF message not sent as connection is shutting down");
|
||||
}
|
||||
@ -621,7 +632,7 @@ public class OnvifConnection {
|
||||
public void eventRecieved(String eventMessage) {
|
||||
String topic = Helper.fetchXML(eventMessage, "Topic", "tns1:");
|
||||
if (topic.isEmpty()) {
|
||||
sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
|
||||
sendOnvifRequest(RequestType.Renew, subscriptionXAddr);
|
||||
return;
|
||||
}
|
||||
String dataName = Helper.fetchXML(eventMessage, "tt:Data", "Name=\"");
|
||||
@ -751,7 +762,7 @@ public class OnvifConnection {
|
||||
default:
|
||||
logger.debug("Please report this camera has an un-implemented ONVIF event. Topic:{}", topic);
|
||||
}
|
||||
sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
|
||||
sendOnvifRequest(RequestType.Renew, subscriptionXAddr);
|
||||
}
|
||||
|
||||
public boolean supportsPTZ() {
|
||||
@ -898,11 +909,11 @@ public class OnvifConnection {
|
||||
logger.debug("ONVIF was not connected when a PTZ request was made, connecting now");
|
||||
connect(usingEvents);
|
||||
}
|
||||
sendOnvifRequest(requestBuilder(requestType, ptzXAddr));
|
||||
sendOnvifRequest(requestType, ptzXAddr);
|
||||
}
|
||||
|
||||
public void sendEventRequest(RequestType requestType) {
|
||||
sendOnvifRequest(requestBuilder(requestType, eventXAddr));
|
||||
sendOnvifRequest(requestType, eventXAddr);
|
||||
}
|
||||
|
||||
public void connect(boolean useEvents) {
|
||||
@ -911,9 +922,9 @@ public class OnvifConnection {
|
||||
if (!isConnected) {
|
||||
logger.debug("Connecting {} to ONVIF", ipAddress);
|
||||
threadPool = Executors.newScheduledThreadPool(2);
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetSystemDateAndTime, deviceXAddr));
|
||||
sendOnvifRequest(RequestType.GetSystemDateAndTime, deviceXAddr);
|
||||
usingEvents = useEvents;
|
||||
sendOnvifRequest(requestBuilder(RequestType.GetCapabilities, deviceXAddr));
|
||||
sendOnvifRequest(RequestType.GetCapabilities, deviceXAddr);
|
||||
}
|
||||
} finally {
|
||||
connecting.unlock();
|
||||
@ -951,7 +962,7 @@ public class OnvifConnection {
|
||||
if (bootstrap != null) {
|
||||
if (usingEvents && !mainEventLoopGroup.isShuttingDown()) {
|
||||
// Some cameras may continue to send events even when they can't reach a server.
|
||||
sendOnvifRequest(requestBuilder(RequestType.Unsubscribe, subscriptionXAddr));
|
||||
sendOnvifRequest(RequestType.Unsubscribe, subscriptionXAddr);
|
||||
}
|
||||
// give time for the Unsubscribe request to be sent, shutdownGracefully will try to send it first.
|
||||
threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS);
|
||||
|
Loading…
Reference in New Issue
Block a user