From 679f1e71a26ac3f4ce2e2d375a65fa34c669b90f Mon Sep 17 00:00:00 2001 From: Lee Mitchell <34962558+Codemonkey1973@users.noreply.github.com> Date: Wed, 22 Dec 2021 16:25:32 +0000 Subject: [PATCH] First release --- Changelog.txt | 2 + Makefile | 34 + README.md | 167 ++++ common.h | 46 + main.c | 694 ++++++++++++++ occ.exe | Bin 0 -> 312790 bytes orlaco.c | 2540 +++++++++++++++++++++++++++++++++++++++++++++++++ orlaco.h | 296 ++++++ 8 files changed, 3779 insertions(+) create mode 100644 Changelog.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 common.h create mode 100644 main.c create mode 100644 occ.exe create mode 100644 orlaco.c create mode 100644 orlaco.h diff --git a/Changelog.txt b/Changelog.txt new file mode 100644 index 0000000..2f871da --- /dev/null +++ b/Changelog.txt @@ -0,0 +1,2 @@ +December 22nd 2021 + First version released. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13951b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# +# Copyright 2021 Lee Mitchell +# This file is part of OCC (Orlaco Camera Configurator) +# +# OCC 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 3 of the License, +# or (at your option) any later version. +# +# OCC 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 OCC. If not, +# see . +# +############################################################################ + +TARGET=occ.exe + +CC=gcc + +all: +ifeq ($(OS),Windows_NT) + $(CC) -o $(TARGET) main.c orlaco.c -lws2_32 +else + $(CC) -o $(TARGET) main.c orlaco.c +endif + +clean: + rm -rf $(TARGET) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a434aae --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# OCC (Orlaco Camera Configurator) + +This application can be used to configure some aspects of Orlaco EMOS IP Cameras. + +### Background + +I was working on a project that required multiple rugged IP cameras and +ended up ordering some Orlaco EMOS IP cameras. These looked ideal for the +application, but I didn't really know much about them in advance. + +When they arrived, I found that configuring them required a PC application +that Orlaco themselves don't supply (or if they do, I've not been able to find it). +So, after some research I discovered they use ISO 17215 protocol which sits on top +of SOME/IP protocol, and that configuration utilities are available commercially. +I obtained a quote for one such utility that came in at around 1000 Euros. +Rather a lot I thought, and far more than we wanted to pay, so as any self +respecting software engineer would do, with the help of Google and Wireshark, +I knocked up this utility to allow various settings to be changed. + +Note: I don't have access to ISO17215, I've only seen the snippets that are +available for free on the various websites selling it, as such there are some +features that I don't have documentation for. If anyone has +access to a commercial configuration utility and a copy of wireshark, logs +and more info would be very welcome so that more features can be completed. + +Hopefully this will be useful to someone, please let me know if so! + +### Binaries +I've included a binary built for 64 bit Windows. Linux binaries +are not included, but I have tested that it builds and works under Ubuntu. + + +### Use +Open a command prompt and type something like this: + +occ.exe --help + +This will give you a list of command line options. + +### Examples + +#### Discovering cameras on a network: + +.\occ.exe -d 192.168.2.255 +Found 2 devices +0: IP=192.168.2.10 Type=01 ServiceID=433f InstanceID=000a V=1.0 +1: IP=192.168.2.11 Type=01 ServiceID=433f InstanceID=000b V=1.0 + +#### Read all registers on the camera with IP address 192.168.2.10 + +.\occ.exe -R : -i 192.168.2.10 +Read registers 0 to 64 + +Registers +Index Address Hex Decimal Ascii Name +00 0xb00c 0x01 1 LED Mode +01 0xb041 0x00 0 Stream Protocol +02 0xb042 0xc0 192 IP Address 0 +03 0xb043 0xa8 168 IP Address 1 +04 0xb044 0x02 2 IP Address 2 +05 0xb045 0x0a 10 IP Address 3 +06 0xb046 0xff 255 Network Mask 0 +07 0xb047 0xff 255 Network Mask 1 +08 0xb048 0xff 255 Network Mask 2 +09 0xb049 0x00 0 Network Mask 3 +10 0xb04a 0x70 112 p MAC Address 0 +11 0xb04b 0xb3 179 MAC Address 1 +12 0xb04c 0xd5 213 MAC Address 2 +13 0xb04d 0x4f 79 O MAC Address 3 +14 0xb04e 0x53 83 S MAC Address 4 +15 0xb04f 0x34 52 4 MAC Address 5 +16 0xb055 0x00 0 VLAN ID 0 +17 0xb056 0x00 0 VLAN ID 1 +18 0xb057 0x45 69 E Stream ID 0 +19 0xb058 0x4d 77 M Stream ID 1 +20 0xb059 0x4f 79 O Stream ID 2 +21 0xb05a 0x53 83 S Stream ID 3 +22 0xb05b 0x20 32 Stream ID 4 +23 0xb05c 0x20 32 Stream ID 5 +24 0xb05d 0x20 32 Stream ID 6 +25 0xb05e 0x20 32 Stream ID 7 +26 0xb05f 0xc0 192 Destination IP Address 0 +27 0xb060 0xa8 168 Destination IP Address 1 +28 0xb061 0x02 2 Destination IP Address 2 +29 0xb062 0x01 1 Destination IP Address 3 +30 0xb063 0xff 255 Destination MAC Address 0 +31 0xb064 0xff 255 Destination MAC Address 1 +32 0xb065 0xff 255 Destination MAC Address 2 +33 0xb066 0xff 255 Destination MAC Address 3 +34 0xb067 0xff 255 Destination MAC Address 4 +35 0xb068 0xff 255 Destination MAC Address 5 +36 0xb069 0xc3 195 Destination Port 0 +37 0xb06a 0x54 84 T Destination Port 1 +38 0xb06b 0x01 1 Selected ROI +39 0xb06c 0x00 0 No Stream At Boot ? +40 0xb06d 0x43 67 C UDP Communication Port 0 +41 0xb06e 0x3f 63 ? UDP Communication Port 1 +42 0xb06f 0xc3 195 RTP Stream Source Port 0 +43 0xb070 0x54 84 T RTP Stream Source Port 1 +44 0xb071 0x01 1 HDR ? +45 0xb072 0x01 1 Overlay ? +46 0xb073 0x00 0 DHCP Enabled ? +47 0xb078 0x00 0 Wait For MAC ? +48 0xb079 0x00 0 Wait For PTP Sync ? +49 0xb171 0x4f 79 O DHCP Hostname 0 +50 0xb172 0x72 114 r DHCP Hostname 1 +51 0xb173 0x6c 108 l DHCP Hostname 2 +52 0xb174 0x61 97 a DHCP Hostname 3 +53 0xb175 0x63 99 c DHCP Hostname 4 +54 0xb176 0x6f 111 o DHCP Hostname 5 +55 0xb177 0x20 32 DHCP Hostname 6 +56 0xb178 0x45 69 E DHCP Hostname 7 +57 0xb179 0x4d 77 M DHCP Hostname 8 +58 0xb17a 0x4f 79 O DHCP Hostname 9 +59 0xb17b 0x53 83 S DHCP Hostname 10 +60 0xb17c 0x20 32 DHCP Hostname 11 +61 0xb17d 0x20 32 DHCP Hostname 12 +62 0xb17e 0x20 32 DHCP Hostname 13 +63 0xb17f 0x20 32 DHCP Hostname 14 +64 0xb180 0x20 32 DHCP Hostname 15 + +#### Read the LED Mode register + +.\occ.exe -r 0 -i 192.168.2.10 +Read register 0 + +Registers +Index Address Hex Decimal Ascii Name +00 0xb00c 0x01 1 LED Mode + + +#### Write the LED Mode register + +.\occ -w 0=0 -i 192.168.2.10 +Write register 0 value 00 + +Registers +Index Address Hex Decimal Ascii Name +00 0xb00c 0x00 0 LED Mode + + +#### Read all regions of interest + + .\occ.exe -G : +Read ROI's 1 to 10 + +Regions Of Interest +ROI P1X P1Y P2X P2Y Width Height Mbps Fps Mode +1 0 0 1280 960 1280 960 5 25 2 +2 0 0 1280 960 1280 960 5 25 1 +3 0 960 1280 0 1280 960 50 30 1 +4 0 0 1280 960 800 600 50 30 1 +5 1280 0 0 960 800 600 50 30 1 +6 0 960 1280 0 800 600 50 30 1 +7 0 0 1280 960 640 480 50 30 1 +8 1280 0 0 960 640 480 50 30 1 +9 0 960 1280 0 640 480 50 30 1 +10 1280 960 0 0 1280 960 50 30 1 + +#### Configure region of interest 10 + + .\occ.exe -s 10=1280,960,0,0,1280,960,60,25,2 + +Regions Of Interest +ROI P1X P1Y P2X P2Y Width Height Mbps Fps Mode +10 1280 960 0 0 1280 960 60 25 2 + diff --git a/common.h b/common.h new file mode 100644 index 0000000..cd5c744 --- /dev/null +++ b/common.h @@ -0,0 +1,46 @@ +#ifndef COMMON_H +#define COMMON_H + +/****************************************************************************/ +/*** Include files ***/ +/****************************************************************************/ + +#include + +/****************************************************************************/ +/*** Macro Definitions ***/ +/****************************************************************************/ + +/*--------------------------------------------------------------------------*/ +/* Boolean Constants */ +/*--------------------------------------------------------------------------*/ + +#if !defined FALSE && !defined TRUE +#define TRUE (1) /* page 207 K+R 2nd Edition */ +#define FALSE (0) +#endif /* !defined FALSE && #if !defined TRUE */ + +/*--------------------------------------------------------------------------*/ +/* Null pointers */ +/*--------------------------------------------------------------------------*/ + +/* NULL data pointer */ +#if !defined NULL +#define NULL ( (void *) 0 ) +#endif + +/****************************************************************************/ +/*** Type Definitions ***/ +/****************************************************************************/ + +typedef uint8_t bool_t; + +/****************************************************************************/ +/*** Exported Functions ***/ +/****************************************************************************/ + +#endif // COMMON_H + +/****************************************************************************/ +/*** END OF FILE ***/ +/****************************************************************************/ diff --git a/main.c b/main.c new file mode 100644 index 0000000..ae53cd1 --- /dev/null +++ b/main.c @@ -0,0 +1,694 @@ +/**************************************************************************** + * + * Copyright 2021 Lee Mitchell + * This file is part of OCC (Orlaco Camera Configurator) + * + * OCC (Orlaco Camera Configurator) 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 3 of the License, + * or (at your option) any later version. + * + * OCC (Orlaco Camera Configurator) 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 OCC (Orlaco Camera Configurator). If not, + * see . + * + ****************************************************************************/ + +/****************************************************************************/ +/*** Include files ***/ +/****************************************************************************/ + +#include +#include +#include +#include +#include +#include "common.h" +#include "orlaco.h" + +#ifdef _WIN32 +#include +#endif + +/****************************************************************************/ +/*** Macro Definitions ***/ +/****************************************************************************/ + +/****************************************************************************/ +/*** Type Definitions ***/ +/****************************************************************************/ + +typedef enum +{ + E_STATUS_OK, + E_STATUS_AGAIN, + E_STATUS_ERROR_TIMEOUT, + E_STATUS_ERROR_WRITING, + E_STATUS_OUT_OF_RANGE, + E_STATUS_NULL_PARAMETER, + E_STATUS_FAIL +} teStatus; + +typedef enum{ + E_VERBOSITY_ERRORS_ONLY = -1, + E_VERBOSITY_LOW, + E_VERBOSITY_MEDIUM, + E_VERBOSITY_HIGH +} teVerbosity; + +typedef struct +{ + volatile bool_t bExitRequest; + volatile bool_t bExit; + bool_t bDiscoverCameras; + bool_t bReadRegisters; + bool_t bWriteRegisters; + bool_t bReadRegionsOfInterest; + bool_t bWriteRegionsOfInterest; + bool_t bSetCameraMode; + teVerbosity eVerbosity; + char *pstrIpAddress; + int iPort; + ORLACO_tsInstance sOrlaco; +} tsInstance; + +/****************************************************************************/ +/*** Local Function Prototypes ***/ +/****************************************************************************/ + +static void vParseCommandLineOptions(tsInstance *psInstance, int argc, char *argv[]); + +#ifdef _WIN32 +static BOOL WINAPI bCtrlHandler(DWORD dwCtrlType); +#endif + +static void vPrintRegisterDefinitions(ORLACO_tsInstance *psInstance); +static bool_t bIsPrintable(char c); + +/****************************************************************************/ +/*** Exported Variables ***/ +/****************************************************************************/ + +/****************************************************************************/ +/*** Local Variables ***/ +/****************************************************************************/ + +static tsInstance sInstance; + +/****************************************************************************/ +/*** Exported Functions ***/ +/****************************************************************************/ + +/**************************************************************************** + * + * NAME: main + * + * DESCRIPTION: + * Application entry point + * + * RETURNS: + * int + * + ****************************************************************************/ +int main(int argc, char *argv[]) +{ + // int udp_socket; + // struct sockaddr_in si_other; + // int slen = sizeof(si_other); + +#ifdef _WIN32 + WSADATA wsaData; +#endif + + int n; + + bool_t bOk = TRUE; + ORLACO_tuIP auIP[10]; + ORLACO_tsRegionOfInterest sROI; + + int iNumCameras = 0; + + /* Initialise application state and set some defaults */ + sInstance.bReadRegisters = FALSE; + sInstance.bWriteRegisters = FALSE; + sInstance.bReadRegionsOfInterest = FALSE; + sInstance.bWriteRegionsOfInterest = FALSE; + + sInstance.bExit = FALSE; + sInstance.eVerbosity = E_VERBOSITY_MEDIUM; + sInstance.pstrIpAddress = "127.0.0.1"; + +#ifdef _WIN32 + /* Create the socket */ + WSAStartup(MAKEWORD(2, 2), &wsaData); + + SetConsoleCtrlHandler(bCtrlHandler, TRUE); +#endif + + + + // Need to do this before parsing command line options + ORLACO_bInit(&sInstance.sOrlaco, "192.168.2.10", "192.168.2.255", ORLACO_DEFAULT_PORT); + + // Set the default service ID + sInstance.sOrlaco.u16ServiceID = 0x433f; + + /* Parse the command line options */ + vParseCommandLineOptions(&sInstance, argc, argv); + + if(bOk && sInstance.bDiscoverCameras) + { + bOk &= ORLACO_bDiscover(&sInstance.sOrlaco); + } + + if(bOk && (sInstance.bWriteRegisters || sInstance.bWriteRegionsOfInterest)) + { + bOk &= ORLACO_bSetCamExclusive(&sInstance.sOrlaco, 100); + } + + if(bOk && sInstance.bWriteRegisters) + { + bOk &= ORLACO_bSetRegisters(&sInstance.sOrlaco); + } + + if(bOk && sInstance.bReadRegisters) + { + bOk &= ORLACO_bGetRegisters(&sInstance.sOrlaco); + } + + if(bOk && sInstance.bWriteRegionsOfInterest) + { + bOk &= ORLACO_bSetRegionsOfInterest(&sInstance.sOrlaco); + } + + if(bOk && sInstance.bReadRegionsOfInterest) + { + bOk &= ORLACO_bGetRegionsOfInterest(&sInstance.sOrlaco); + } + + if(bOk && (sInstance.bWriteRegisters || sInstance.bWriteRegionsOfInterest)) + { + bOk &= ORLACO_bEraseCamExclusive(&sInstance.sOrlaco); + } + + if(bOk && sInstance.bDiscoverCameras) + { + printf("Found %d devices\n", sInstance.sOrlaco.u16NumCameras); + for(n = 0; n < sInstance.sOrlaco.u16NumCameras; n++) + { + printf("%d: IP=%d.%d.%d.%d Type=%02x ServiceID=%04x InstanceID=%04x V=%d.%d\n", + n, + sInstance.sOrlaco.psCameras[n].uIP.au8IP[3], + sInstance.sOrlaco.psCameras[n].uIP.au8IP[2], + sInstance.sOrlaco.psCameras[n].uIP.au8IP[1], + sInstance.sOrlaco.psCameras[n].uIP.au8IP[0], + sInstance.sOrlaco.psCameras[n].sDiscoveryServiceEntry.u8Type, + sInstance.sOrlaco.psCameras[n].sDiscoveryServiceEntry.u16ServiceID, + sInstance.sOrlaco.psCameras[n].sDiscoveryServiceEntry.u16InstanceID, + sInstance.sOrlaco.psCameras[n].sDiscoveryServiceEntry.u8MajorVersion, + sInstance.sOrlaco.psCameras[n].sDiscoveryServiceEntry.u32MinorVersion); + } + } + + if(bOk && (sInstance.bReadRegisters || sInstance.bWriteRegisters)) + { + if(sInstance.eVerbosity >= E_VERBOSITY_MEDIUM) printf("\nRegisters\nIndex\tAddress\tHex\tDecimal\tAscii\tName\n"); + for(n = 0; n < sInstance.sOrlaco.u16NumRegisters; n++) + { + if(sInstance.sOrlaco.psRegisters[n].bRead || sInstance.sOrlaco.psRegisters[n].bWrite) + { + printf("%02d\t0x%04x\t0x%02x\t%3d\t%c\t%s\n", n, sInstance.sOrlaco.psRegisters[n].u16Address, sInstance.sOrlaco.psRegisters[n].u8Value, sInstance.sOrlaco.psRegisters[n].u8Value, bIsPrintable(sInstance.sOrlaco.psRegisters[n].u8Value) ? sInstance.sOrlaco.psRegisters[n].u8Value : ' ', sInstance.sOrlaco.psRegisters[n].pcDescription); + } + } + } + + if(bOk && (sInstance.bReadRegionsOfInterest || sInstance.bWriteRegionsOfInterest)) + { + if(sInstance.eVerbosity >= E_VERBOSITY_MEDIUM) printf("\nRegions Of Interest\nROI\tP1X\tP1Y\tP2X\tP2Y\tWidth\tHeight\tMbps\tFps\tMode\n"); + + for(n = 1; n < sInstance.sOrlaco.u16NumRegionsOfInterest; n++) + { + if(sInstance.sOrlaco.psRegionsOfInterest[n].bRead || sInstance.sOrlaco.psRegionsOfInterest[n].bWrite) + { + printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", + n, + sInstance.sOrlaco.psRegionsOfInterest[n].u16P1X, + sInstance.sOrlaco.psRegionsOfInterest[n].u16P1Y, + sInstance.sOrlaco.psRegionsOfInterest[n].u16P2X, + sInstance.sOrlaco.psRegionsOfInterest[n].u16P2Y, + sInstance.sOrlaco.psRegionsOfInterest[n].u16OutputWidth, + sInstance.sOrlaco.psRegionsOfInterest[n].u16OutputHeight, + sInstance.sOrlaco.psRegionsOfInterest[n].u32MaxBitrate, + sInstance.sOrlaco.psRegionsOfInterest[n].u8FrameRate, + sInstance.sOrlaco.psRegionsOfInterest[n].eCompressionMode); + } + + } + } + + if(bOk && sInstance.bSetCameraMode) + { + bOk &= ORLACO_bSetCamMode(&sInstance.sOrlaco, sInstance.sOrlaco.eCameraMode); + } + + ORLACO_vDeInit(&sInstance.sOrlaco); + + + + + + /* Main program loop, execute until we get a signal requesting to exit */ + // while(!sInstance.bExit) + // { + + // if(sInstance.bExitRequest) + // { + // sInstance.bExitRequest = FALSE; + // } + + // } + +#ifdef _WIN32 + /* Tidy up and then exit */ + WSACleanup(); +#endif + + return EXIT_SUCCESS; +} + + +/****************************************************************************/ +/*** Local Functions ***/ +/****************************************************************************/ +/**************************************************************************** + * + * NAME: vParseCommandLineOptions + * + * DESCRIPTION: + * Parse command line options + * + * RETURNS: + * void + * + ****************************************************************************/ +static void vParseCommandLineOptions(tsInstance *psInstance, int argc, char *argv[]) +{ + + int n; + int c; + char *token, *fromStr, *toStr, *ipStr, *portStr; + int index, value, from, to, port; + + static const struct option lopts[] = { + { "discover", required_argument, 0, 'd' }, + { "write-reg", required_argument, 0, 'w' }, + { "read-reg", required_argument, 0, 'r' }, + { "read-regs", required_argument, 0, 'R' }, + + { "get-roi", required_argument, 0, 'g' }, + { "get-rois", required_argument, 0, 'G' }, + { "set-roi", required_argument, 0, 's' }, + + { "ip", required_argument, 0, 'i' }, + { "service-id", required_argument, 0, 'e' }, + + { "set-mode", required_argument, 0, 'm' }, + + { "verbosity", required_argument, 0, 'v' }, + + { "help", required_argument, 0, 'h' }, + { "help", required_argument, 0, '?' }, + + { NULL, 0, 0, 0 }, + }; + + + while(1) + { + + c = getopt_long(argc, argv, "d:w:r:R:g:G:s:i:e:m:v:?:h:", lopts, NULL); + + if (c == -1) + break; + + switch(c) + { + + case 'd': + port = ORLACO_DEFAULT_PORT; + ipStr = strtok(optarg, ":"); + portStr = strtok(NULL, ":"); + if(portStr != NULL) + { + port = (uint16_t)atoi(portStr); + } + if(!ORLACO_bSetBroadcastIP(&psInstance->sOrlaco, ipStr, port)) + { + printf("Error: Failed to set the broadcast IP to %s:%d\n", ipStr, port); + exit(EXIT_FAILURE); + } + psInstance->bDiscoverCameras = TRUE; + break; + + case 'i': + port = ORLACO_DEFAULT_PORT; + ipStr = strtok(optarg, ":"); + portStr = strtok(NULL, ":"); + if(portStr != NULL) + { + port = (uint16_t)atoi(portStr); + } + if(!ORLACO_bSetUnicastIP(&psInstance->sOrlaco, ipStr, port)) + { + printf("Error: Failed to set the camera IP to %s:%d\n", ipStr, port); + exit(EXIT_FAILURE); + } + break; + + case 'e': + psInstance->sOrlaco.u16ServiceID = 0x433f; + psInstance->sOrlaco.u16ServiceID = (uint16_t)atoi(optarg); + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Service ID set to 0x%04x\n", psInstance->sOrlaco.u16ServiceID); + break; + + case 'w': + token = strtok(optarg, "="); + index = (uint8_t)atoi(token); + value = (uint8_t)atoi(strtok(NULL, "=")); + if(index < psInstance->sOrlaco.u16NumRegisters) + { + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Write register %d value %02x\n", index, value); + psInstance->sOrlaco.psRegisters[index].u8Value = value; + psInstance->sOrlaco.psRegisters[index].bWrite = TRUE; + } + else + { + printf("Error: Register index %d is out of range, max is %d\n", index, psInstance->sOrlaco.u16NumRegisters - 1); + exit(EXIT_FAILURE); + } + psInstance->bWriteRegisters |= TRUE; + break; + + case 'r': + index = (uint8_t)atoi(optarg); + if(index < psInstance->sOrlaco.u16NumRegisters) + { + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Read register %d\n", index); + psInstance->sOrlaco.psRegisters[index].bRead = TRUE; + } + else + { + printf("Error: Register index %d is out of range, max is %d\n", index, psInstance->sOrlaco.u16NumRegisters - 1); + exit(EXIT_FAILURE); + } + psInstance->bReadRegisters |= TRUE; + break; + + case 'R': + from = 0; + to = psInstance->sOrlaco.u16NumRegisters - 1; + fromStr = strtok(optarg, ":"); + if(fromStr != NULL) + { + from = atoi(fromStr); + } + toStr = strtok(NULL, ":"); + if(toStr != NULL) + { + to = atoi(toStr); + } + if(from >= psInstance->sOrlaco.u16NumRegisters) + { + printf("Error: From value %d is out of range, max is %d\n", from, psInstance->sOrlaco.u16NumRegisters - 1); + exit(EXIT_FAILURE); + } + if(to >= psInstance->sOrlaco.u16NumRegisters) + { + printf("Error: To value %d is out of range, max is %d\n", to, psInstance->sOrlaco.u16NumRegisters - 1); + exit(EXIT_FAILURE); + } + if(from >= to) + { + printf("Error: To value %d must be greater than from value %d\n", to, from); + exit(EXIT_FAILURE); + } + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Read registers %d to %d\n", from, to); + for(n = from; n <= to; n++) + { + psInstance->sOrlaco.psRegisters[n].bRead = TRUE; + } + psInstance->bReadRegisters |= TRUE; + break; + + case 'g': + index = (uint8_t)atoi(optarg); + if((index > 0) && (index < psInstance->sOrlaco.u16NumRegisters)) + { + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Get ROI %d\n", index); + psInstance->sOrlaco.psRegionsOfInterest[index].bRead = TRUE; + } + else + { + printf("Error: ROI index %d is out of range, min is 1, max is %d\n", index, psInstance->sOrlaco.u16NumRegionsOfInterest - 1); + exit(EXIT_FAILURE); + } + psInstance->bReadRegionsOfInterest |= TRUE; + break; + + case 'G': + from = 1; + to = psInstance->sOrlaco.u16NumRegionsOfInterest - 1; + fromStr = strtok(optarg, ":"); + if(fromStr != NULL) + { + from = atoi(fromStr); + } + toStr = strtok(NULL, ":"); + if(toStr != NULL) + { + to = atoi(toStr); + } + if((from < 1) || (from >= psInstance->sOrlaco.u16NumRegionsOfInterest)) + { + printf("Error: From value %d is out of range, min is 1, max is %d\n", from, psInstance->sOrlaco.u16NumRegionsOfInterest - 1); + exit(EXIT_FAILURE); + } + if((to < 1) || (to >= psInstance->sOrlaco.u16NumRegionsOfInterest)) + { + printf("Error: To value %d is out of range, min is 1, max is %d\n", to, psInstance->sOrlaco.u16NumRegionsOfInterest - 1); + exit(EXIT_FAILURE); + } + if(from >= to) + { + printf("Error: To value %d must be greater than from value %d\n", to, from); + exit(EXIT_FAILURE); + } + if(psInstance->eVerbosity >= E_VERBOSITY_MEDIUM) printf("Read ROI's %d to %d\n", from, to); + for(n = from; n <= to; n++) + { + psInstance->sOrlaco.psRegionsOfInterest[n].bRead = TRUE; + } + psInstance->bReadRegionsOfInterest |= TRUE; + break; + + case 's': + { + index = (uint8_t)atoi(strtok(optarg, "=")); + + if((index > 0) && (index < psInstance->sOrlaco.u16NumRegisters)) + { + + psInstance->sOrlaco.psRegionsOfInterest[index].u16P1X = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u16P1Y = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u16P2X = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u16P2Y = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u16OutputWidth = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u16OutputHeight = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u32MaxBitrate = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].u8FrameRate = atoi(strtok(NULL, ",")); + psInstance->sOrlaco.psRegionsOfInterest[index].eCompressionMode = (ORLACO_teVideoCompressionMode)atoi(strtok(NULL, ",")); + + if(psInstance->eVerbosity >= E_VERBOSITY_HIGH) printf("Set ROI %d P1X=%d P1Y=%d P2X=%d P2Y=%d Width=%d Height=%d MaxBitRate=%dMb/s FrameRate=%dfps Mode=%d\n", + index, + psInstance->sOrlaco.psRegionsOfInterest[index].u16P1X, + psInstance->sOrlaco.psRegionsOfInterest[index].u16P1Y, + psInstance->sOrlaco.psRegionsOfInterest[index].u16P2X, + psInstance->sOrlaco.psRegionsOfInterest[index].u16P2Y, + psInstance->sOrlaco.psRegionsOfInterest[index].u16OutputWidth, + psInstance->sOrlaco.psRegionsOfInterest[index].u16OutputHeight, + psInstance->sOrlaco.psRegionsOfInterest[index].u32MaxBitrate, + psInstance->sOrlaco.psRegionsOfInterest[index].u8FrameRate, + psInstance->sOrlaco.psRegionsOfInterest[index].eCompressionMode); + psInstance->sOrlaco.psRegionsOfInterest[index].bWrite = TRUE; + psInstance->bWriteRegionsOfInterest |= TRUE; + } + else + { + printf("Error: ROI index %d is out of range, min is 1, max is %d\n", index, psInstance->sOrlaco.u16NumRegionsOfInterest - 1); + exit(EXIT_FAILURE); + } + } + break; + + case 'm': + psInstance->sOrlaco.eCameraMode = (ORLACO_teCameraMode)atoi(optarg); + psInstance->bSetCameraMode = TRUE; + break; + + case 'v': + switch(atoi(optarg)) + { + case -1: + psInstance->eVerbosity = E_VERBOSITY_ERRORS_ONLY; + ORLACO_vSetVerbosity(&psInstance->sOrlaco, E_ORLACO_VERBOSITY_ERRORS_ONLY); + break; + + case 0: + psInstance->eVerbosity = E_VERBOSITY_LOW; + ORLACO_vSetVerbosity(&psInstance->sOrlaco, E_ORLACO_VERBOSITY_ERRORS_ONLY); + break; + + case 1: + psInstance->eVerbosity = E_VERBOSITY_MEDIUM; + ORLACO_vSetVerbosity(&psInstance->sOrlaco, E_ORLACO_VERBOSITY_INFO); + break; + + case 2: + psInstance->eVerbosity = E_VERBOSITY_HIGH; + ORLACO_vSetVerbosity(&psInstance->sOrlaco, E_ORLACO_VERBOSITY_DEBUG); + break; + + default: + psInstance->eVerbosity = E_VERBOSITY_HIGH; + ORLACO_vSetVerbosity(&psInstance->sOrlaco, E_ORLACO_VERBOSITY_DEBUG); + break; + + } + break; + + case '?': + case 'h': + default: + printf("+----------------------------------------------------------------------+\n" \ + "| OCC (Orlaco Camera Configurator) |\n" \ + "| Copyright (C) 2021 Lee Mitchell |\n" \ + "| |\n" \ + "| This program comes with ABSOLUTELY NO WARRANTY. |\n" \ + "| This is free software, and you are welcome to redistribute it |\n" \ + "| under certain conditions; See the GNU General Public License |\n" \ + "| version 3 or later for more details. You should have received a copy |\n" \ + "| of the GNU General Public License along with OCC (Orlaco Camera |\n" \ + "| Configurator) If not, see . |\n" \ + "+----------------------------------------------------------------------+\n\n"); + + if((optarg != NULL) && (strcasecmp(optarg, "regs") == 0)) + { + vPrintRegisterDefinitions(&psInstance->sOrlaco); + } + else + { + printf("\nUsage: %s \n\n", argv[0]); + puts(" -d --discover : Discover cameras using broadcast IP:Port(17215 default)\n\n" + " -i --ip : Set the IP address and port of the camera, e.g. 192.168.2.1:17215\n\n" + " -e --service-id Set the service id to \n\n" + " -r --read-reg Read the value from register \n\n" + " -R --read-regs : Read the value from register at index to index \n\n" + " -w --write-reg = Write into register \n\n" + " -g --get-roi Read the Region Of Interest at index \n\n" + " -G --read-rois : Read the Region Of Interest at index to index \n\n" + " -s --set-roi =,,,,,,,,\n" + " Write region of interest at index \n\n" + " -v --verbosity Set verbosity level -1, 0, 1 & 2 are valid\n\n" + " -q --quiet Enable quiet mode (no updates on console)\n\n" + " -d --debug Enable debugging mode (extra console messages)\n\n" + " -? --help Display help\n\n" + " --help regs Display the list of registers available\n\n" + ); + } + + exit(EXIT_FAILURE); + break; + } + + } + +} + + +/**************************************************************************** + * + * NAME: bCtrlHandler + * + * DESCRIPTION: + * Handles Ctrl+C events + * + * RETURNS: + * BOOL + * + ****************************************************************************/ +#ifdef _WIN32 +static BOOL WINAPI bCtrlHandler(DWORD dwCtrlType) +{ + switch (dwCtrlType) + { + + case CTRL_C_EVENT: + case CTRL_SHUTDOWN_EVENT: + printf("\nExit requested\n"); + sInstance.bExitRequest = TRUE; + return TRUE; + + case CTRL_BREAK_EVENT: + printf("\nImmediate exit requested\n"); + sInstance.bExit = TRUE; + return TRUE; + + default: + return FALSE; + } +} +#endif + +/**************************************************************************** + * + * NAME: vPrintRegisterDefinitions + * + * DESCRIPTION: + * Prints out a list of the registers in the Orlaco Emos camera + * + * RETURNS: + * void + * + ****************************************************************************/ +static void vPrintRegisterDefinitions(ORLACO_tsInstance *psInstance) +{ + int n; + + printf("Orlaco EMOS Camera Registers:\n\n"\ + "Index\tDescription\n"\ + "-----\t-----------\n"); + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + printf("%02d\t%s %s\n", n, psInstance->psRegisters[n].pcDescription, psInstance->psRegisters[n].pcHelp); + } + +} + +static bool_t bIsPrintable(char c) +{ + if(c >=32 && c <=127) + { + return TRUE; + } + return FALSE; +} + +/****************************************************************************/ +/*** END OF FILE ***/ +/****************************************************************************/ + diff --git a/occ.exe b/occ.exe new file mode 100644 index 0000000000000000000000000000000000000000..f93156299970bbf2961dd1ae81f9fc3faa223996 GIT binary patch literal 312790 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 literal 0 HcmV?d00001 diff --git a/orlaco.c b/orlaco.c new file mode 100644 index 0000000..020e948 --- /dev/null +++ b/orlaco.c @@ -0,0 +1,2540 @@ +/**************************************************************************** + * + * Copyright 2021 Lee Mitchell + * This file is part of OCC (Orlaco Camera Configurator) + * + * OCC (Orlaco Camera Configurator) 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 3 of the License, + * or (at your option) any later version. + * + * OCC (Orlaco Camera Configurator) 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 OCC (Orlaco Camera Configurator). If not, + * see . + * + ****************************************************************************/ + +/****************************************************************************/ +/*** Include files ***/ +/****************************************************************************/ + +#include +#include +#include +#include "common.h" +#include "orlaco.h" + +/****************************************************************************/ +/*** Macro Definitions ***/ +/****************************************************************************/ + +#define ORLACO_SOCKET_READ_TIMEOUT_MS (100) + +/****************************************************************************/ +/*** Type Definitions ***/ +/****************************************************************************/ + +// SOME/IP Methods +typedef enum { + E_ORLACO_METHOD_ID_GET_DATA_SHEET = 0x0001, + E_ORLACO_METHOD_ID_GET_CAM_STATUS = 0x0002, + E_ORLACO_METHOD_ID_SET_CAM_MODE = 0x0003, // Start, Stop, Restart the camera application + E_ORLACO_METHOD_ID_SET_CAM_EXCLUSIVE = 0x0011, + E_ORLACO_METHOD_ID_ERASE_CAM_EXCLUSIVE = 0x0019, + E_ORLACO_METHOD_ID_SET_HOST_PARAMETERS = 0x0022, + E_ORLACO_METHOD_ID_GET_HOST_PARAMETERS = 0x0024, + E_ORLACO_METHOD_ID_ERASE_HOST_PARAMETERS = 0x0029, + E_ORLACO_METHOD_ID_SET_REGION_OF_INTEREST = 0x0101, + E_ORLACO_METHOD_ID_SET_REGIONS_OF_INTEREST = 0x0102, + E_ORLACO_METHOD_ID_GET_REGION_OF_INTEREST = 0x0103, + E_ORLACO_METHOD_ID_GET_REGIONS_OF_INTEREST = 0x0104, + E_ORLACO_METHOD_ID_ERASE_REGION_OF_INTEREST = 0x0109, + E_ORLACO_METHOD_ID_SET_VIDEO_FORMAT = 0x0111, + E_ORLACO_METHOD_ID_GET_VIDEO_FORMAT = 0x0113, + E_ORLACO_METHOD_ID_ERASE_VIDEO_FORMAT = 0x0119, + E_ORLACO_METHOD_ID_SET_HISTOGRAMM_FORMAT = 0x0121, + E_ORLACO_METHOD_ID_GET_HISTOGRAMM_FORMAT = 0x0123, + E_ORLACO_METHOD_ID_ERASE_HISTOGRAMM_FORMAT = 0x0129, + E_ORLACO_METHOD_ID_SUBSCRIBE_ROI_VIDEO = 0x0131, + E_ORLACO_METHOD_ID_UNSUBSCRIBE_ROI_VIDEO = 0x0132, + E_ORLACO_METHOD_ID_SUBSCRIBE_ROI_HISTOGRAMM = 0x0133, + E_ORLACO_METHOD_ID_UNSUBSCRIBE_ROI_HISTOGRAMM = 0x0134, + E_ORLACO_METHOD_ID_SET_CAM_CONTROL = 0x0201, + E_ORLACO_METHOD_ID_SET_CAM_CONTROLS = 0x0202, + E_ORLACO_METHOD_ID_GET_CAM_CONTROL = 0x0203, + E_ORLACO_METHOD_ID_GET_CAM_CONTROLS = 0x0204, + E_ORLACO_METHOD_ID_SET_CAM_REGISTER = 0x0301, + E_ORLACO_METHOD_ID_SET_CAM_REGISTERS = 0x0302, + E_ORLACO_METHOD_ID_GET_CAM_REGISTER = 0x0303, + E_ORLACO_METHOD_ID_GET_CAM_REGISTERS = 0x0304, + E_ORLACO_METHOD_ID_SET_USED_REGISTER_SET = 0x0305, + E_ORLACO_METHOD_ID_SERVICE_DISCOVERY = 0x8100, +} ORLACO_teMethodID; + + +// SOME/IP Message Type +typedef enum { + E_ORLACO_MESSAGE_TYPE_REQUEST = 0x00, + E_ORLACO_MESSAGE_TYPE_REQUEST_NO_RETURN = 0x01, + E_ORLACO_MESSAGE_TYPE_NOTIFICATION = 0x02, + E_ORLACO_MESSAGE_TYPE_RESPONSE = 0x80, + E_ORLACO_MESSAGE_TYPE_ERROR = 0x81, + E_ORLACO_MESSAGE_TYPE_TP_REQUEST = 0x20, + E_ORLACO_MESSAGE_TYPE_TP_REQUEST_NO_RETURN = 0x21, + E_ORLACO_MESSAGE_TYPE_TP_NOTIFICATION = 0x22, + E_ORLACO_MESSAGE_TYPE_TP_RESPONSE = 0x23, + E_ORLACO_MESSAGE_TYPE_TP_ERROR = 0x24, +} ORLACO_teMessageType; + + +// Make sure these remain in the same order as the register indexes +typedef enum { + E_ORLACO_REGISTER_ADDRESS_LED_MODE = 0xb00c, + E_ORLACO_REGISTER_ADDRESS_STREAM_PROTOCOL = 0xb041, + E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_0 = 0xb042, + E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_1 = 0xb043, + E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_2 = 0xb044, + E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_3 = 0xb045, + E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_0 = 0xb046, + E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_1 = 0xb047, + E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_2 = 0xb048, + E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_3 = 0xb049, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_0 = 0xb04a, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_1 = 0xb04b, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_2 = 0xb04c, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_3 = 0xb04d, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_4 = 0xb04e, + E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_5 = 0xb04f, + E_ORLACO_REGISTER_ADDRESS_VLAN_ID_0 = 0xb055, + E_ORLACO_REGISTER_ADDRESS_VLAN_ID_1 = 0xb056, + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_0 = 0xb057, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_1 = 0xb058, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_2 = 0xb059, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_3 = 0xb05a, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_4 = 0xb05b, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_5 = 0xb05c, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_6 = 0xb05d, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_STREAM_ID_7 = 0xb05e, // 0xb057 - 0xb05e inclusive + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_0 = 0xb05f, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_1 = 0xb060, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_2 = 0xb061, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_3 = 0xb062, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_0 = 0xb063, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_1 = 0xb064, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_2 = 0xb065, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_3 = 0xb066, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_4 = 0xb067, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_5 = 0xb068, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_PORT_0 = 0xb069, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_PORT_1 = 0xb06a, + E_ORLACO_REGISTER_ADDRESS_SELECTED_ROI = 0xb06b, + E_ORLACO_REGISTER_ADDRESS_NO_STREAM_AT_BOOT = 0xb06c, + E_ORLACO_REGISTER_ADDRESS_UDP_COMMUNICATION_PORT_0 = 0xb06d, + E_ORLACO_REGISTER_ADDRESS_UDP_COMMUNICATION_PORT_1 = 0xb06e, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_SOURCE_PORT_0 = 0xb06f, + E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_SOURCE_PORT_1 = 0xb070, + E_ORLACO_REGISTER_ADDRESS_HDR = 0xb071, + E_ORLACO_REGISTER_ADDRESS_OVERLAY = 0xb072, + E_ORLACO_REGISTER_ADDRESS_DHCP = 0xb073, + E_ORLACO_REGISTER_ADDRESS_WAIT_FOR_MAC = 0xb078, + E_ORLACO_REGISTER_ADDRESS_WAIT_FOR_PTP_SYNC = 0xb079, + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_0 = 0xb171, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_1 = 0xb172, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_2 = 0xb173, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_3 = 0xb174, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_4 = 0xb175, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_5 = 0xb176, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_6 = 0xb177, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_7 = 0xb178, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_8 = 0xb179, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_9 = 0xb17a, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_10 = 0xb17b, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_11 = 0xb17c, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_12 = 0xb17d, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_13 = 0xb17e, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_14 = 0xb17f, // 0xb171 - 0xb180 inclusive + E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_15 = 0xb180, // 0xb171 - 0xb180 inclusive +} ORLACO_teRegisterAddress; + + +typedef struct { + uint16_t u16Address; + char *pcDescription; + char *pcHelp; +} ORLACO_tsRegisterDefinition; + +typedef struct { + uint16_t u16Qtty; + uint16_t au16RegisterAddress[ORLACO_MAX_REGISTERS]; +} ORLACO_tsRegisterRequestsPayload; + +typedef struct { + uint16_t u16Qtty; + ORLACO_tsRegisterValue asRegisterValues[ORLACO_MAX_REGISTERS]; +} ORLACO_tsGetRegistersResponsePayload; + +typedef struct { + uint16_t u16Qtty; + ORLACO_tsRegisterValue asRegisterValues[ORLACO_MAX_REGISTERS]; +} ORLACO_tsSetRegistersRequestPayload; + +typedef struct { + uint32_t u32ExclusiveTime; +} ORLACO_tsSetCamExclusivePayload; + +typedef struct { + uint32_t u32Mode; +} ORLACO_tsSetCamModePayload; + +typedef struct { + uint32_t u32RegionOfInterestIndex; + uint16_t u16P1X; + uint16_t u16P1Y; + uint16_t u16P2X; + uint16_t u16P2Y; + uint8_t u8Unknown1SetTo0x01; + uint8_t u8Unknown2SetTo0x00; + uint16_t u16Unknown3SetTo0x0000; + + uint16_t u16OutputWidth; + uint16_t u16OutputHeight; + + uint8_t u8Unknown4SetTo0x00; + uint8_t u8FrameRate; + uint16_t u16Unknown4bSetTo0x0000; + + uint8_t u8Unknown5SetTo0x00; + uint8_t u8Unknown6SetTo0x02; + uint32_t u32MaxBitrate; // set to 0x00000032 = 50Mb/s + uint8_t u8VideoCompressionMode; // 0x00 = none, 0x01 = JPEG, 0x02 = H.264 + + uint8_t u8Unknown7SetTo0x00; + uint8_t u8Unknown8SetTo0x00; + uint8_t u8Unknown9SetTo0x00; + + uint8_t u8Unknown10SetTo0x04; + uint8_t u8Unknown11SetTo0x01; + + uint16_t u16Unknown12SetTo0x00ff; + +} ORLACO_tsSetRegionOfInterestPayload; + +// New camera, payload from get roi 0 request +// 0000 00 00 00 00 05 00 03 c0 01 00 00 00 05 00 03 c0 +// 0010 00 1e 00 00 00 02 00 00 00 32 01 00 00 00 04 01 +// 0020 00 ff + +typedef struct { + uint32_t u32RegionOfInterest; +} ORLACO_tsGetRegionOfInterestRequestPayload; + + +typedef struct { + uint32_t u32RegionOfInterest; + uint16_t u16P1X; + uint16_t u16P1Y; + uint16_t u16P2X; + uint16_t u16P2Y; + uint8_t u8Unknown1SetTo0x01; + uint8_t u8Unknown2SetTo0x00; + uint16_t u16Unknown3SetTo0x0000; + + uint16_t u16OutputWidth; + uint16_t u16OutputHeight; + + uint8_t u8Unknown4SetTo0x00; + uint8_t u8FrameRate; + uint16_t u16Unknown4bSetTo0x0000; + + uint8_t u8Unknown5SetTo0x00; + uint8_t u8Unknown6SetTo0x02; + uint32_t u32MaxBitrate; // set to 0x00000032 = 50Mb/s + uint8_t u8VideoCompressionMode; // 0x00 = none, 0x01 = MJPEG, 0x02 = H.264 + + uint8_t u8Unknown7SetTo0x00; + uint8_t u8Unknown8SetTo0x00; + uint8_t u8Unknown9SetTo0x00; + + uint8_t u8Unknown10SetTo0x04; + uint8_t u8Unknown11SetTo0x01; + + uint16_t u16Unknown12SetTo0x00ff; +} ORLACO_tsGetRegionOfInterestResponsePayload; + + +typedef struct { + uint32_t u32RegionOfInterest; +} ORLACO_tsSubscribeRegionOfInterestPayload; + + +typedef struct { + uint16_t u16Length; + uint8_t u8Type; + uint8_t u8Reserved; + union { + uint32_t au32Data[2]; + } uData; +} ORLACO_tsServiceDiscoveryServiceOptions; + + +typedef struct { + uint8_t u8Flags; + uint32_t u24Reserved; + uint32_t u32LengthOfEntriesArrayInBytes; + ORLACO_tsServiceDiscoveryServiceEntry asServiceEntry[ORLACO_MAX_SD_SERVICES]; + uint32_t u32LengthOfOptionsArrayInBytes; + ORLACO_tsServiceDiscoveryServiceOptions asServiceOptions[ORLACO_MAX_SD_OPTIONS]; +} ORLACO_tsServiceDiscoveryPayload; + + +typedef struct { + ORLACO_tuIP uSrcAddr; + + uint16_t u16ServiceID; + uint16_t u16MethodID; + uint32_t u32Length; + uint16_t u16ClientID; + uint16_t u16SessionID; + uint8_t u8SomeIPVersion; + uint8_t u8InterfaceVersion; + uint8_t u8MessageType; + uint8_t u8ReturnCode; + + union { + ORLACO_tsSetCamExclusivePayload sSetCamExclusivePayload; + ORLACO_tsSetCamModePayload sSetCamModePayload; + ORLACO_tsRegisterRequestsPayload sRegisterRequestsPayload; + ORLACO_tsGetRegistersResponsePayload sGetRegistersResponsePayload; + ORLACO_tsSetRegistersRequestPayload sSetRegistersRequestPayload; + ORLACO_tsGetRegionOfInterestRequestPayload sGetRegionOfInterestRequestPayload; + ORLACO_tsGetRegionOfInterestResponsePayload sGetRegionOfInterestResponsePayload; + ORLACO_tsSetRegionOfInterestPayload sSetRegionOfInterestPayload; + ORLACO_tsSubscribeRegionOfInterestPayload sSubscribeRegionOfInterestPayload; + ORLACO_tsServiceDiscoveryPayload sServiceDiscoveryPayload; + } uPayload; + +} ORLACO_tsMsg ; + + +typedef struct { + uint32_t u32Length; + uint32_t u32Offset; + uint8_t *pu8Data; +} ORLACO_tsBuffer; + +/****************************************************************************/ +/*** Local Function Prototypes ***/ +/****************************************************************************/ + +static uint16_t ORLACO_u16GetSessionID(ORLACO_tsInstance *psInstance); + +static ORLACO_tsBuffer *ORLACO_psBufferCreate(uint32_t u32DataLength); +static void ORLACO_vBufferDestroy(ORLACO_tsBuffer *psBuffer); + +static bool_t ORLACO_bWriteU8(ORLACO_tsBuffer *psBuffer, uint8_t u8Data); +static bool_t ORLACO_bWriteU16(ORLACO_tsBuffer *psBuffer, uint16_t u16Data); +static bool_t ORLACO_bWriteU24(ORLACO_tsBuffer *psBuffer, uint32_t u24Data); +static bool_t ORLACO_bWriteU32(ORLACO_tsBuffer *psBuffer, uint32_t u32Data); + +static bool_t ORLACO_bReadU8(ORLACO_tsBuffer *psBuffer, uint8_t *pu8Data); +static bool_t ORLACO_bReadU16(ORLACO_tsBuffer *psBuffer, uint16_t *pu16Data); +static bool_t ORLACO_bReadU24(ORLACO_tsBuffer *psBuffer, uint32_t *pu24Data); +static bool_t ORLACO_bReadU32(ORLACO_tsBuffer *psBuffer, uint32_t *pu32Data); + +static bool_t ORLACO_bWriteMessageHeaderIntoBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsMsg *psMsg); +static bool_t ORLACO_bReadMessageHeaderFromBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsMsg *psMsg); +static void ORLACO_vPrintBuffer(ORLACO_tsBuffer *psBuffer); +static bool_t ORLACO_bWriteServiceDiscoveryServiceEntryIntoBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsServiceDiscoveryServiceEntry *psServiceEntry); +static bool_t ORLACO_bReadServiceDiscoveryServiceEntryFromBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsServiceDiscoveryServiceEntry *psServiceEntry); +static bool_t ORLACO_bSendDatagram(UDPSOCKET sktTx, struct sockaddr_in *psDstAddr, ORLACO_tsBuffer *psBuffer); +static bool_t ORLACO_bReceiveDatagram(ORLACO_tsInstance *psInstance, ORLACO_tsMsg *psRxMsg, uint16_t u16MethodID); +static char *ORLACO_pcGetReturnCodeAsString(ORLACO_teReturnCode eReturnCode); +bool_t ORLACO_bIPAlreadyInArray(ORLACO_tsInstance *psInstance, ORLACO_tuIP IP); + +/****************************************************************************/ +/*** Exported Variables ***/ +/****************************************************************************/ + +/****************************************************************************/ +/*** Local Variables ***/ +/****************************************************************************/ + +// A list of all the registers known so far in the Orlaco camera +// Make sure these stay in the same order that they are defined in, otherwise the indexes will be useless. +const ORLACO_tsRegisterDefinition ORLACO_asRegisterDefinitions[] = { + {E_ORLACO_REGISTER_ADDRESS_LED_MODE, "LED Mode", "0=Off, 1=Auto, 2=On"}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_PROTOCOL, "Stream Protocol", "0=RTP, 1=AVB"}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_0, "IP Address 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_1, "IP Address 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_2, "IP Address 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_IP_ADDRESS_3, "IP Address 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_0, "Network Mask 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_1, "Network Mask 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_2, "Network Mask 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_STATIC_NETWORK_MASK_3, "Network Mask 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_0 , "MAC Address 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_1 , "MAC Address 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_2 , "MAC Address 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_3 , "MAC Address 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_4 , "MAC Address 4", ""}, + {E_ORLACO_REGISTER_ADDRESS_MAC_ADDRESS_5 , "MAC Address 5", ""}, + {E_ORLACO_REGISTER_ADDRESS_VLAN_ID_0, "VLAN ID 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_VLAN_ID_1, "VLAN ID 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_0, "Stream ID 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_1, "Stream ID 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_2, "Stream ID 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_3, "Stream ID 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_4, "Stream ID 4", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_5, "Stream ID 5", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_6, "Stream ID 6", ""}, + {E_ORLACO_REGISTER_ADDRESS_STREAM_ID_7, "Stream ID 7", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_0, "Destination IP Address 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_1, "Destination IP Address 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_2, "Destination IP Address 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_IP_ADDRESS_3, "Destination IP Address 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_0,"Destination MAC Address 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_1,"Destination MAC Address 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_2,"Destination MAC Address 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_3,"Destination MAC Address 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_4,"Destination MAC Address 4", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_MAC_ADDRESS_5,"Destination MAC Address 5", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_PORT_0, "Destination Port 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_DESTINATION_PORT_1, "Destination Port 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_SELECTED_ROI, "Selected ROI", "1 to 10"}, + {E_ORLACO_REGISTER_ADDRESS_NO_STREAM_AT_BOOT, "No Stream At Boot ?", "0=Stream, 1=No stream"}, + {E_ORLACO_REGISTER_ADDRESS_UDP_COMMUNICATION_PORT_0, "UDP Communication Port 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_UDP_COMMUNICATION_PORT_1, "UDP Communication Port 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_SOURCE_PORT_0, "RTP Stream Source Port 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_RTP_STREAM_SOURCE_PORT_1, "RTP Stream Source Port 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_HDR, "HDR ?", "0=Disabled, 1=Enabled"}, + {E_ORLACO_REGISTER_ADDRESS_OVERLAY, "Overlay ?", "0=Disabled, 1=Enabled"}, + {E_ORLACO_REGISTER_ADDRESS_DHCP, "DHCP Enabled ?", "0=Disabled, 1=Enabled"}, + {E_ORLACO_REGISTER_ADDRESS_WAIT_FOR_MAC, "Wait For MAC ?", "0=Don't wait, 1=Wait"}, + {E_ORLACO_REGISTER_ADDRESS_WAIT_FOR_PTP_SYNC, "Wait For PTP Sync ?", "0=Don't wait, 1=Wait"}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_0, "DHCP Hostname 0", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_1, "DHCP Hostname 1", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_2, "DHCP Hostname 2", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_3, "DHCP Hostname 3", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_4, "DHCP Hostname 4", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_5, "DHCP Hostname 5", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_6, "DHCP Hostname 6", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_7, "DHCP Hostname 7", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_8, "DHCP Hostname 8", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_9, "DHCP Hostname 9", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_10, "DHCP Hostname 10", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_11, "DHCP Hostname 11", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_12, "DHCP Hostname 12", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_13, "DHCP Hostname 13", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_14, "DHCP Hostname 14", ""}, + {E_ORLACO_REGISTER_ADDRESS_DHCP_HOSTNAME_15, "DHCP Hostname 15", ""}, +}; + +/****************************************************************************/ +/*** Exported Functions ***/ +/****************************************************************************/ + +/**************************************************************************** + * + * NAME: ORLACO_bInit + * + * DESCRIPTION: + * Initialises the Orlaco functions + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bInit(ORLACO_tsInstance *psInstance, char *pcUnicastIP, char *pcBroadcastIP, uint16_t u16DstPort) +{ + int n; + int iEnable; + psInstance->u16DstPort = u16DstPort; + + psInstance->u16NumCameras = 0; + psInstance->psCameras = NULL; + + // Initialise the socket + psInstance->Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + +#ifdef _WIN32 + if (psInstance->Socket == INVALID_SOCKET) +#else + if (psInstance->Socket < 0) +#endif + { + printf("Error: Can't create UDP socket in %s\n", __FUNCTION__); + return FALSE; + } + + // Make sure socket supports broadcasts + iEnable = 1; + if(setsockopt(psInstance->Socket, SOL_SOCKET, SO_BROADCAST, (const char*)&iEnable, sizeof(iEnable)) != 0) + { + printf("Error: Can't set socket options in %s\n", __FUNCTION__); + return FALSE; + } + + // Make sure socket doesn't receive its own broadcasts + // iEnable = 0; + // if(setsockopt(psInstance->Socket, IPPROTO_IP, IP_MULTICAST_LOOP, &iEnable, sizeof(iEnable)) != 0) + // { + // printf("Error: Can't set socket options\n"); + // return FALSE; + // } + +#ifdef _WIN32 + DWORD timeout = ORLACO_SOCKET_READ_TIMEOUT_MS; + if(setsockopt(psInstance->Socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) != 0) + { + printf("Error: Can't set socket options in %s\n", __FUNCTION__); + return FALSE; + } +#else + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = ORLACO_SOCKET_READ_TIMEOUT_MS * 1000; + if(setsockopt(psInstance->Socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) + { + printf("Error: Can't set socket options in %s\n", __FUNCTION__); + return FALSE; + } +#endif + + /* Set the socket address for the server */ + memset((char *) &psInstance->fdServer, 0, sizeof(psInstance->fdServer)); + psInstance->fdServer.sin_family = AF_INET; + psInstance->fdServer.sin_port = htons(u16DstPort); + psInstance->fdServer.sin_addr.s_addr = INADDR_ANY; + + /* Set the socket address for unicast packets */ + memset((char *) &psInstance->fdUnicast, 0, sizeof(psInstance->fdUnicast)); + psInstance->fdUnicast.sin_family = AF_INET; + psInstance->fdUnicast.sin_port = htons(u16DstPort); + psInstance->fdUnicast.sin_addr.s_addr = inet_addr(pcUnicastIP); + + /* Set the socket address for broadcast packets */ + memset((char *) &psInstance->fdBroadcast, 0, sizeof(psInstance->fdBroadcast)); + psInstance->fdBroadcast.sin_family = AF_INET; + psInstance->fdBroadcast.sin_port = htons(u16DstPort); + psInstance->fdBroadcast.sin_addr.s_addr = inet_addr(pcBroadcastIP); + + if(bind(psInstance->Socket, (const struct sockaddr*)&psInstance->fdServer, sizeof(psInstance->fdServer)) < 0) + { + printf("Error: Bind failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Set how many registers we have + psInstance->u16NumRegisters = sizeof(ORLACO_asRegisterDefinitions) / sizeof(ORLACO_tsRegisterDefinition); + + // Allocate the memory for the registers + psInstance->psRegisters = (ORLACO_tsRegisterValue*)malloc(psInstance->u16NumRegisters * sizeof(ORLACO_tsRegisterValue)); + if(psInstance->psRegisters == NULL) + { + printf("Error: Failed to allocate memory for registers in %s\n", __FUNCTION__); + return FALSE; + } + + // Initialise the registers with the correct addresses etc + memset(psInstance->psRegisters, 0, psInstance->u16NumRegisters * sizeof(ORLACO_tsRegisterValue)); + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + psInstance->psRegisters[n].u16Address = ORLACO_asRegisterDefinitions[n].u16Address; + psInstance->psRegisters[n].pcDescription = ORLACO_asRegisterDefinitions[n].pcDescription; + psInstance->psRegisters[n].pcHelp = ORLACO_asRegisterDefinitions[n].pcHelp; + } + + // Set how many regions of interest there are + psInstance->u16NumRegionsOfInterest = ORLACO_NUM_REGIONS_OF_INTEREST; + + // Allocate the memory for the regions of interest + psInstance->psRegionsOfInterest = (ORLACO_tsRegionOfInterest*)malloc(psInstance->u16NumRegionsOfInterest * sizeof(ORLACO_tsRegionOfInterest)); + if(psInstance->psRegionsOfInterest == NULL) + { + printf("Error: Failed to allocate memory for regions of interest in %s\n", __FUNCTION__); + return FALSE; + } + + // Initialise the regions of interest + memset(psInstance->psRegionsOfInterest, 0, psInstance->u16NumRegionsOfInterest * sizeof(ORLACO_tsRegionOfInterest)); + + return TRUE; +} + + +/**************************************************************************** + * + * NAME: ORLACO_vDeInit + * + * DESCRIPTION: + * De-initialises the Orlaco functions + * + * RETURNS: + * void + * + ****************************************************************************/ +void ORLACO_vDeInit(ORLACO_tsInstance *psInstance) +{ + +#ifdef _WIN32 + closesocket(psInstance->Socket); +#else + close(psInstance->Socket); +#endif + + // Free memory allocated for the registers + if(psInstance->psRegisters != NULL) + { + free(psInstance->psRegisters); + } + + // Free memory allocated for the regions of interest + if(psInstance->psRegionsOfInterest != NULL) + { + free(psInstance->psRegionsOfInterest); + } + + // Free memory allocated for discovered cameras + if(psInstance->psCameras != NULL) + { + free(psInstance->psCameras); + } + +} + + +/**************************************************************************** + * + * NAME: ORLACO_vSetVerbosity + * + * DESCRIPTION: + * Set the logging verbosity level + * + * RETURNS: + * void + * + ****************************************************************************/ +void ORLACO_vSetVerbosity(ORLACO_tsInstance *psInstance, ORLACO_eVerbosityLevel eVerbosityLevel) +{ + psInstance->eVerbosity = eVerbosityLevel; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetBroadcastIP + * + * DESCRIPTION: + * Sets the broadcast IP address + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetBroadcastIP(ORLACO_tsInstance *psInstance, char *pcIpAddress, uint16_t u16DstPort) +{ + /* Set the socket address for broadcast packets */ + memset((char *) &psInstance->fdBroadcast, 0, sizeof(psInstance->fdBroadcast)); + psInstance->fdBroadcast.sin_family = AF_INET; + psInstance->fdBroadcast.sin_port = htons(u16DstPort); + psInstance->fdBroadcast.sin_addr.s_addr = inet_addr(pcIpAddress); + + return TRUE; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetUnicastIP + * + * DESCRIPTION: + * Sets the unicast IP address + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetUnicastIP(ORLACO_tsInstance *psInstance, char *pcIpAddress, uint16_t u16DstPort) +{ + /* Set the socket address for unicast packets */ + memset((char *) &psInstance->fdUnicast, 0, sizeof(psInstance->fdUnicast)); + psInstance->fdUnicast.sin_family = AF_INET; + psInstance->fdUnicast.sin_port = htons(u16DstPort); + psInstance->fdUnicast.sin_addr.s_addr = inet_addr(pcIpAddress); + + return TRUE; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bBufferTest + * + * DESCRIPTION: + * Function for testing the buffer read/write functions + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bBufferTest(ORLACO_tsInstance *psInstance) +{ + + uint8_t u8; + uint16_t u16; + uint32_t u24; + uint32_t u32; + + bool_t bOk = TRUE; + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + bOk &= ORLACO_bWriteU8(psBuffer, 0x11); + bOk &= ORLACO_bWriteU16(psBuffer, 0x2233); + bOk &= ORLACO_bWriteU24(psBuffer, 0x445566); + bOk &= ORLACO_bWriteU32(psBuffer, 0x778899aa); + if(!bOk){ + ORLACO_vBufferDestroy(psBuffer); + return bOk; + } + + printf("Buffer contains %d bytes after writing:", psBuffer->u32Offset); + for(int n = 0; n < psBuffer->u32Offset; n++) + { + printf(" %02x", psBuffer->pu8Data[n]); + } + printf("\n"); + + psBuffer->u32Offset = 0; + + bOk &= ORLACO_bReadU8(psBuffer, &u8); + bOk &= ORLACO_bReadU16(psBuffer, &u16); + bOk &= ORLACO_bReadU24(psBuffer, &u24); + bOk &= ORLACO_bReadU32(psBuffer, &u32); + + printf("u8=%02x u16=%04x u24=%06x, u32=%08x\n", u8, u16, u24, u32); + + // Free the buffer + ORLACO_vBufferDestroy(psBuffer); + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bDiscover + * + * DESCRIPTION: + * Sends a broadcast discovery message to identify cameras + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bDiscover(ORLACO_tsInstance *psInstance) +{ + + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Clear any previous discovery results + psInstance->u16NumCameras = 0; + if(psInstance->psCameras != NULL) + { + free(psInstance->psCameras); + psInstance->psCameras = NULL; + } + + psInstance->u16ServiceID = 0xffff; + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SERVICE_DISCOVERY; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_NOTIFICATION; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + + sMsg.uPayload.sServiceDiscoveryPayload.u8Flags = 0; + sMsg.uPayload.sServiceDiscoveryPayload.u24Reserved = 0; + sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes = ORLACO_SD_OPTION_LENGTH * 1; + sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfOptionsArrayInBytes = 0; + + // Service entry 0 + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u8Type = 0; // Find + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u8Index1stOptions = 0; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u8Index2ndOptions = 0; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u8NumberOfOptions = 0; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u16ServiceID = psInstance->u16ServiceID; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u16InstanceID = 0xffff; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u8MajorVersion = 0xff; + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u24TTL = 3600; // 1 hour + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u32MinorVersion = 0xffffffff; // Any + + sMsg.u32Length += 12 + sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes + sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfOptionsArrayInBytes; + + + // Write the message header into the byte buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sServiceDiscoveryPayload.u8Flags); + bOk &= ORLACO_bWriteU24(psBuffer, sMsg.uPayload.sServiceDiscoveryPayload.u24Reserved); + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes); + + bOk &= ORLACO_bWriteServiceDiscoveryServiceEntryIntoBuffer(psBuffer, &sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0]); + + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfOptionsArrayInBytes); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdBroadcast, psBuffer)) + { + return FALSE; + } + + while(ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_MESSAGE_TYPE_NOTIFICATION)) + { + // If we got some options but the service id is 0xffff, probably our own broadcast so drop it + if((sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes >= ORLACO_SD_OPTION_LENGTH) && (sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0].u16ServiceID == 0xffff)) + { + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("Skipping message since its probably our own broadcast in %s\n", __FUNCTION__); + continue; + } + + // If we've already seen a message from this camera, skip it + if(ORLACO_bIPAlreadyInArray(psInstance, sMsg.uSrcAddr)) + { + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("Skipping message since we've already seen this camera before in %s\n", __FUNCTION__); + continue; + } + + psInstance->u16NumCameras++; + psInstance->psCameras = realloc(psInstance->psCameras, sizeof(ORLACO_tsCamera) * psInstance->u16NumCameras); + psInstance->psCameras[psInstance->u16NumCameras-1].uIP.u32IP = sMsg.uSrcAddr.u32IP; + if(sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes >= ORLACO_SD_OPTION_LENGTH) + { + memcpy(&psInstance->psCameras[psInstance->u16NumCameras-1].sDiscoveryServiceEntry, &sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[0], sizeof(ORLACO_tsServiceDiscoveryServiceEntry)); + } + else + { + memset(&psInstance->psCameras[psInstance->u16NumCameras-1].sDiscoveryServiceEntry, 0, sizeof(ORLACO_tsServiceDiscoveryServiceEntry)); + } + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_INFO) printf("Got response from IP %d.%d.%d.%d Len=%d\n", sMsg.uSrcAddr.au8IP[3], sMsg.uSrcAddr.au8IP[2], sMsg.uSrcAddr.au8IP[1], sMsg.uSrcAddr.au8IP[0], sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes); + + for(int n = 0; n < sMsg.uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes; n += ORLACO_SD_OPTION_LENGTH) + { + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_INFO) printf("Option %d Type=%02x ServiceID=%04x InstanceID=%04x V=%d.%d 1stOptionsIdx=%d 2ndOptionsIdx=%d OptionsNum=%02x\n\n", + n, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u8Type, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u16ServiceID, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u16InstanceID, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u8MajorVersion, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u32MinorVersion, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u8Index1stOptions, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u8Index2ndOptions, + sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u8NumberOfOptions + ); + psInstance->u16ServiceID = sMsg.uPayload.sServiceDiscoveryPayload.asServiceEntry[n].u16ServiceID; + } + } + + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetCamExclusive + * + * DESCRIPTION: + * Sends a "Set Camera Exclusive" message containing the specified exclusive time + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetCamExclusive(ORLACO_tsInstance *psInstance, uint32_t u32ExclusiveTime) +{ + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SET_CAM_EXCLUSIVE; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Add the message payload and adjust the length field to include it + sMsg.u32Length += sizeof(sMsg.uPayload.sSetCamExclusivePayload); + sMsg.uPayload.sSetCamExclusivePayload.u32ExclusiveTime = u32ExclusiveTime; + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the payload into the buffer + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sSetCamExclusivePayload.u32ExclusiveTime); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_SET_CAM_EXCLUSIVE); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bEraseCamExclusive + * + * DESCRIPTION: + * Sends a "Erase Camera Exclusive" message + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bEraseCamExclusive(ORLACO_tsInstance *psInstance) +{ + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_ERASE_CAM_EXCLUSIVE; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_ERASE_CAM_EXCLUSIVE); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetCamMode + * + * DESCRIPTION: + * Sends a "Set Camera Mode" message containing the specified mode + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetCamMode(ORLACO_tsInstance *psInstance, ORLACO_teCameraMode eMode) +{ + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + // sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16ServiceID = 0xffff; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SET_CAM_MODE; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Add the message payload and adjust the length field to include it + sMsg.u32Length += sizeof(sMsg.uPayload.sSetCamModePayload); + // sMsg.u32Length += 1; + sMsg.uPayload.sSetCamModePayload.u32Mode = eMode; + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the payload into the buffer + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sSetCamModePayload.u32Mode); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_SET_CAM_MODE); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bGetRegisters + * + * DESCRIPTION: + * Reads registers from the camera + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bGetRegisters(ORLACO_tsInstance *psInstance) +{ + bool_t bOk = TRUE; + int n; + ORLACO_tsMsg sMsg; + uint16_t u16Qtty = 0; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // See how many registers we will be reading + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + if(psInstance->psRegisters[n].bRead) u16Qtty++; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_GET_CAM_REGISTERS; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Adjust the length field to include the quantity of registers and list of register adresses + sMsg.u32Length += sizeof(uint16_t) + (u16Qtty * sizeof(uint16_t)); + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the number of registers into the buffer + bOk &= ORLACO_bWriteU16(psBuffer, u16Qtty); + + // Write the register addresses into the buffer + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + // If the register is marked for reading, add its address to the payload + if(psInstance->psRegisters[n].bRead) + { + bOk &= ORLACO_bWriteU16(psBuffer, psInstance->psRegisters[n].u16Address); + } + } + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response, exit if not + if(!ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_GET_CAM_REGISTERS)) + { + return FALSE; + } + + // Check we got back the same number of registers that we requested + if(sMsg.uPayload.sGetRegistersResponsePayload.u16Qtty != u16Qtty) + { + return FALSE; + } + + // Copy register values into original request ensuring the correct value goes with the correct address in case the camera doesn't send things back in the same order + for(int n = 0; n < psInstance->u16NumRegisters; n++) + { + for(int x = 0; x < sMsg.uPayload.sGetRegistersResponsePayload.u16Qtty; x++) + { + if(sMsg.uPayload.sGetRegistersResponsePayload.asRegisterValues[x].u16Address == psInstance->psRegisters[n].u16Address) + { + psInstance->psRegisters[n].u8Value = sMsg.uPayload.sGetRegistersResponsePayload.asRegisterValues[x].u8Value; + } + } + } + + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetRegisters + * + * DESCRIPTION: + * Writes registers on the camera + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetRegisters(ORLACO_tsInstance *psInstance) +{ + bool_t bOk = TRUE; + int n; + ORLACO_tsMsg sMsg; + uint16_t u16Qtty = 0; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // See how many registers we will be reading + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + if(psInstance->psRegisters[n].bWrite) u16Qtty++; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SET_CAM_REGISTERS; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Adjust the length field to include the quantity of registers and list of register adresses + sMsg.u32Length += sizeof(uint16_t) + (u16Qtty * 4); + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the number of registers into the buffer + bOk &= ORLACO_bWriteU16(psBuffer, u16Qtty); + + // Write the register addresses and their values into the buffer + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + // If the register is marked for writing, add its address and value to the payload + if(psInstance->psRegisters[n].bWrite) + { + bOk &= ORLACO_bWriteU16(psBuffer, psInstance->psRegisters[n].u16Address); + bOk &= ORLACO_bWriteU8(psBuffer, psInstance->psRegisters[n].u8Padding); + bOk &= ORLACO_bWriteU8(psBuffer, psInstance->psRegisters[n].u8Value); + } + } + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_SET_CAM_REGISTERS); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bGetAllRegisters + * + * DESCRIPTION: + * Reads all the registers on the camera + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bGetAllRegisters(ORLACO_tsInstance *psInstance) +{ + + bool_t bOk = TRUE; + int n; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + for(n = 0; n < psInstance->u16NumRegisters; n++) + { + psInstance->psRegisters[n].bRead = TRUE; + } + + + bOk &= ORLACO_bGetRegisters(psInstance); + + for(int n = 0; n < psInstance->u16NumRegisters; n++) + { + printf("%02d) Addr=%04x Value=%02x - %3d - %c\t%s\n", n, psInstance->psRegisters[n].u16Address, psInstance->psRegisters[n].u8Value, psInstance->psRegisters[n].u8Value, psInstance->psRegisters[n].u8Value, psInstance->psRegisters[n].pcDescription); + } + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bGetRegionOfInterest + * + * DESCRIPTION: + * Gets the specified region of interest from the camera + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bGetRegionOfInterest(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterest, ORLACO_tsRegionOfInterest *psRegionOfInterest) +{ + + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_GET_REGION_OF_INTEREST; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Add the message payload and adjust the length field to include it + sMsg.u32Length += sizeof(sMsg.uPayload.sGetRegionOfInterestRequestPayload); + sMsg.uPayload.sGetRegionOfInterestRequestPayload.u32RegionOfInterest = u32RegionOfInterest; + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the payload into the buffer + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sGetRegionOfInterestRequestPayload.u32RegionOfInterest); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response, exit if not + if(!ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_GET_REGION_OF_INTEREST)) + { + return FALSE; + } + + psRegionOfInterest->u16P1X = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P1X; + psRegionOfInterest->u16P1Y = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P1Y; + psRegionOfInterest->u16P2X = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P2X; + psRegionOfInterest->u16P2Y = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P2Y; + psRegionOfInterest->u16OutputWidth = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16OutputWidth; + psRegionOfInterest->u16OutputHeight = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16OutputHeight; + psRegionOfInterest->eCompressionMode = (ORLACO_teVideoCompressionMode)sMsg.uPayload.sGetRegionOfInterestResponsePayload.u8VideoCompressionMode; + psRegionOfInterest->u32MaxBitrate = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u32MaxBitrate; + psRegionOfInterest->u8FrameRate = sMsg.uPayload.sGetRegionOfInterestResponsePayload.u8FrameRate; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("P1X=%d P1Y=%d P2X=%d P2Y=%d OutputWidth=%d OutputHeight=%d MaxBitRate=%d FrameRate=%d CompressionMode=%d LastWord=%04x\n", + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P1X, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P1Y, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P2X, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16P2Y, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16OutputWidth, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16OutputHeight, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u32MaxBitrate, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u8FrameRate, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u8VideoCompressionMode, + sMsg.uPayload.sGetRegionOfInterestResponsePayload.u16Unknown12SetTo0x00ff + ); + + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bGetRegionsOfInterest + * + * DESCRIPTION: + * Get the regions of interest marked for reading + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bGetRegionsOfInterest(ORLACO_tsInstance *psInstance) +{ + + bool_t bOk = TRUE; + int n; + + for(n = 1; n < psInstance->u16NumRegionsOfInterest; n++) + { + if(psInstance->psRegionsOfInterest[n].bRead) + { + bOk &= ORLACO_bGetRegionOfInterest(psInstance, n, &psInstance->psRegionsOfInterest[n]); + } + } + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSetRegionsOfInterest + * + * DESCRIPTION: + * Set the regions of interest marked for writing + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetRegionsOfInterest(ORLACO_tsInstance *psInstance) +{ + + bool_t bOk = TRUE; + int n; + + for(n = 1; n < psInstance->u16NumRegionsOfInterest; n++) + { + if(psInstance->psRegionsOfInterest[n].bWrite) + { + bOk &= ORLACO_bSetRegionOfInterest(psInstance, n, &psInstance->psRegionsOfInterest[n]); + } + } + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSubscribeRoiVideo + * + * DESCRIPTION: + * Subscribe the selected region of interest + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSubscribeRoiVideo(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterest) +{ + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SUBSCRIBE_ROI_VIDEO; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + // Add the message payload and adjust the length field to include it + sMsg.u32Length += sizeof(sMsg.uPayload.sSubscribeRegionOfInterestPayload); + sMsg.uPayload.sSubscribeRegionOfInterestPayload.u32RegionOfInterest = u32RegionOfInterest; + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the payload into the buffer + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sSubscribeRegionOfInterestPayload.u32RegionOfInterest); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= !ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_SUBSCRIBE_ROI_VIDEO); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bGetRegionOfInterest + * + * DESCRIPTION: + * Writes the specified region of interest to the camera + * + * RETURNS: + * bool_t TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +bool_t ORLACO_bSetRegionOfInterest(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterestIndex, ORLACO_tsRegionOfInterest *psRegionOfInterest) +{ + bool_t bOk = TRUE; + ORLACO_tsMsg sMsg; + + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("%s()\n", __FUNCTION__); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + // Construct the message header + sMsg.u16ServiceID = psInstance->u16ServiceID; + sMsg.u16MethodID = E_ORLACO_METHOD_ID_SET_REGION_OF_INTEREST; + + sMsg.u32Length = 8; + + sMsg.u16ClientID = psInstance->u16ClientID; + sMsg.u16SessionID = ORLACO_u16GetSessionID(psInstance); + + sMsg.u8SomeIPVersion = 1; + sMsg.u8InterfaceVersion = 1; + sMsg.u8MessageType = E_ORLACO_MESSAGE_TYPE_REQUEST; + sMsg.u8ReturnCode = E_ORLACO_RETURN_CODE_OK; + + sMsg.uPayload.sSetRegionOfInterestPayload.u32RegionOfInterestIndex = u32RegionOfInterestIndex; + sMsg.uPayload.sSetRegionOfInterestPayload.u16P1X = psRegionOfInterest->u16P1X; + sMsg.uPayload.sSetRegionOfInterestPayload.u16P1Y = psRegionOfInterest->u16P1Y; + sMsg.uPayload.sSetRegionOfInterestPayload.u16P2X = psRegionOfInterest->u16P2X; + sMsg.uPayload.sSetRegionOfInterestPayload.u16P2Y = psRegionOfInterest->u16P2Y; + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown1SetTo0x01 = 0x01; // no idea what this does yet, if set to 0, return code is 0x32 - invalid value in video format + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown2SetTo0x00 = 0x00; // no idea what this does yet, if set to 1, return code is 0x32 - invalid value in video format + sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown3SetTo0x0000 = 0x0000; // no idea what this does yet, if set to 1, return code is 0x32 - invalid value in video format + sMsg.uPayload.sSetRegionOfInterestPayload.u16OutputWidth = psRegionOfInterest->u16OutputWidth; + sMsg.uPayload.sSetRegionOfInterestPayload.u16OutputHeight = psRegionOfInterest->u16OutputHeight; + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown4SetTo0x00 = 0x00; // no idea what this does yet + sMsg.uPayload.sSetRegionOfInterestPayload.u8FrameRate = psRegionOfInterest->u8FrameRate; + sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown4bSetTo0x0000 = 0x0000; // no idea what this does yet + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown5SetTo0x00 = 0x00; // no idea what this does yet + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown6SetTo0x02 = 0x02; // no idea what this does yet, if set to 1, return code is 0x32 - invalid value in video format + sMsg.uPayload.sSetRegionOfInterestPayload.u32MaxBitrate = psRegionOfInterest->u32MaxBitrate; + sMsg.uPayload.sSetRegionOfInterestPayload.u8VideoCompressionMode = (uint8_t)psRegionOfInterest->eCompressionMode; + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown7SetTo0x00 = 0x00; // no idea what this does yet, setting to 1 returns ok + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown8SetTo0x00 = 0x00; // no idea what this does yet, setting to 1 returns ok + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown9SetTo0x00 = 0x00; // no idea what this does yet, setting to 1 returns ok + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown10SetTo0x04 = 0x04; // no idea what this does yet + sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown11SetTo0x01 = 0x01; // no idea what this does yet + sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown12SetTo0x00ff = 0x00ff; // no idea what this does yet + + + // Adjust the length field to include the payload + sMsg.u32Length += 38; + + // printf("Len=%d %d\n", sMsg.u32Length, sizeof(ORLACO_tsSetRegionOfInterestPayload)); + + // Write the message header into the byte array buffer + bOk &= ORLACO_bWriteMessageHeaderIntoBuffer(psBuffer, &sMsg); + + // Write the payload into the buffer + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u32RegionOfInterestIndex); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16P1X); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16P1Y); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16P2X); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16P2Y); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown1SetTo0x01); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown2SetTo0x00); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown3SetTo0x0000); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16OutputWidth); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16OutputHeight); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown4SetTo0x00); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8FrameRate); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown4bSetTo0x0000); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown5SetTo0x00); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown6SetTo0x02); + bOk &= ORLACO_bWriteU32(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u32MaxBitrate); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8VideoCompressionMode); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown7SetTo0x00); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown8SetTo0x00); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown9SetTo0x00); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown10SetTo0x04); + bOk &= ORLACO_bWriteU8(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u8Unknown11SetTo0x01); + bOk &= ORLACO_bWriteU16(psBuffer, sMsg.uPayload.sSetRegionOfInterestPayload.u16Unknown12SetTo0x00ff); + + // If we couldn't write the message to the buffer for some reason, free the buffer and then exit + if(!bOk) + { + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // Send the message + if(!ORLACO_bSendDatagram(psInstance->Socket, &psInstance->fdUnicast, psBuffer)) + { + return FALSE; + } + + // See if we get a response + bOk &= ORLACO_bReceiveDatagram(psInstance, &sMsg, E_ORLACO_METHOD_ID_SET_REGION_OF_INTEREST); + + return bOk; + +} + +/****************************************************************************/ +/*** Local Functions ***/ +/****************************************************************************/ + +/**************************************************************************** + * + * NAME: ORLACO_u16GetSessionID + * + * DESCRIPTION: + * Gets a session ID + * + * RETURNS: + * uint16_t + * + ****************************************************************************/ +static uint16_t ORLACO_u16GetSessionID(ORLACO_tsInstance *psInstance) +{ + // increment the session ID. Add extra 1 if that rolls us over to zero since 0 = no session ID + psInstance->u16SessionID++; + if(psInstance->u16SessionID == 0) + { + psInstance->u16SessionID = 1; + } + return psInstance->u16SessionID; +} + + +/**************************************************************************** + * + * NAME: ORLACO_psBufferCreate + * + * DESCRIPTION: + * Creates a buffer + * + * RETURNS: + * ORLACO_tsBuffer* A pointer to a buffer, or NULL if something failed + * + ****************************************************************************/ +static ORLACO_tsBuffer *ORLACO_psBufferCreate(uint32_t u32DataLength) +{ + // Try and allocate some memory for a buffer. Exit if this fails + ORLACO_tsBuffer *psBuffer = (ORLACO_tsBuffer*)malloc(sizeof(ORLACO_tsBuffer)); + if(psBuffer == NULL) + { + return NULL; + } + + // Initialise to 0 + memset(psBuffer, 0, sizeof(ORLACO_tsBuffer)); + + // Try and allocate the memory for the data. If this fails, free the buffer and return + psBuffer->pu8Data = (uint8_t*)malloc(u32DataLength); + if(psBuffer->pu8Data == NULL) + { + free(psBuffer); + return NULL; + } + + psBuffer->u32Length = u32DataLength; + + return psBuffer; +} + + +/**************************************************************************** + * + * NAME: ORLACO_vBufferDestroy + * + * DESCRIPTION: + * Destroys a buffer + * + * RETURNS: + * void + * + ****************************************************************************/ +static void ORLACO_vBufferDestroy(ORLACO_tsBuffer *psBuffer) +{ + // Don't try and free a buffer that hasn't been allocated! + if(psBuffer == NULL) + { + return; + } + + // Only free the data buffer if it was successfully allocated in the first place + if(psBuffer->pu8Data != NULL) + { + free(psBuffer->pu8Data); + } + + // Now we can free the buffer + free(psBuffer); +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteU8 + * + * DESCRIPTION: + * Writes an 8 bit value into a buffer + * + * RETURNS: + * bool_t - TRUE if the data was written, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteU8(ORLACO_tsBuffer *psBuffer, uint8_t u8Data) +{ + + uint8_t *pu8WritePtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to write past the end of the buffer + if((psBuffer->u32Offset + 1) >= psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Write the data into the buffer and adjust the offset value + *pu8WritePtr = u8Data; + psBuffer->u32Offset += 1; + + // Return success + return TRUE; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteU16 + * + * DESCRIPTION: + * Writes a 16 bit value into a buffer + * + * RETURNS: + * bool_t - TRUE if the data was written, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteU16(ORLACO_tsBuffer *psBuffer, uint16_t u16Data) +{ + uint8_t *pu8WritePtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to write past the end of the buffer + if((psBuffer->u32Offset + 1) >= psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Write the data into the buffer and adjust the offset value + *pu8WritePtr = (u16Data >> 8) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u16Data >> 0) & 0xff; + psBuffer->u32Offset += 2; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteU24 + * + * DESCRIPTION: + * Writes a 24 bit value into a buffer + * + * RETURNS: + * bool_t - TRUE if the data was written, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteU24(ORLACO_tsBuffer *psBuffer, uint32_t u24Data) +{ + uint8_t *pu8WritePtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to write past the end of the buffer + if((psBuffer->u32Offset + 1) >= psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Write the data into the buffer and adjust the offset value + *pu8WritePtr = (u24Data >> 16) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u24Data >> 8) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u24Data >> 0) & 0xff; + psBuffer->u32Offset += 3; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteU32 + * + * DESCRIPTION: + * Writes a 32 bit value into a buffer + * + * RETURNS: + * bool_t - TRUE if the data was written, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteU32(ORLACO_tsBuffer *psBuffer, uint32_t u32Data) +{ + uint8_t *pu8WritePtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to write past the end of the buffer + if((psBuffer->u32Offset + 1) >= psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Write the data into the buffer and adjust the offset value + *pu8WritePtr = (u32Data >> 24) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u32Data >> 16) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u32Data >> 8) & 0xff; + pu8WritePtr++; + *pu8WritePtr = (u32Data >> 0) & 0xff; + psBuffer->u32Offset += 4; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadU8 + * + * DESCRIPTION: + * Reads an 8 bit value from a buffer + * + * RETURNS: + * bool_t - TRUE if the data was read, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadU8(ORLACO_tsBuffer *psBuffer, uint8_t *pu8Data) +{ + uint8_t *pu8ReadPtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to read past the end of the buffer + if((psBuffer->u32Offset + 1) > psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Read the data from the buffer + *pu8Data = *pu8ReadPtr; + psBuffer->u32Offset += 1; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadU16 + * + * DESCRIPTION: + * Reads a 16 bit value from a buffer + * + * RETURNS: + * bool_t - TRUE if the data was read, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadU16(ORLACO_tsBuffer *psBuffer, uint16_t *pu16Data) +{ + uint16_t u16Data = 0; + uint8_t *pu8ReadPtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to read past the end of the buffer + if((psBuffer->u32Offset + 2) > psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Read the data from the buffer + u16Data = (*(pu8ReadPtr++)) << 8; + u16Data |= (*(pu8ReadPtr++)) << 0; + + *pu16Data = u16Data; + psBuffer->u32Offset += 2; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadU24 + * + * DESCRIPTION: + * Reads a 24 bit value from a buffer + * + * RETURNS: + * bool_t - TRUE if the data was read, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadU24(ORLACO_tsBuffer *psBuffer, uint32_t *pu24Data) +{ + uint32_t u24Data = 0; + uint8_t *pu8ReadPtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to read past the end of the buffer + if((psBuffer->u32Offset + 3) > psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Read the data from the buffer + u24Data = (*(pu8ReadPtr++)) << 16; + u24Data |= (*(pu8ReadPtr++)) << 8; + u24Data |= (*(pu8ReadPtr++)) << 0; + + *pu24Data = u24Data; + psBuffer->u32Offset += 3; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadU32 + * + * DESCRIPTION: + * Reads a 32 bit value from a buffer + * + * RETURNS: + * bool_t - TRUE if the data was read, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadU32(ORLACO_tsBuffer *psBuffer, uint32_t *pu32Data) +{ + uint32_t u32Data = 0; + uint8_t *pu8ReadPtr = psBuffer->pu8Data + psBuffer->u32Offset; + + // Check we're not about to read past the end of the buffer + if((psBuffer->u32Offset + 4) > psBuffer->u32Length) + { + // Return failure + return FALSE; + } + + // Read the data from the buffer + u32Data = (*(pu8ReadPtr++)) << 24; + u32Data |= (*(pu8ReadPtr++)) << 16; + u32Data |= (*(pu8ReadPtr++)) << 8; + u32Data |= (*(pu8ReadPtr++)) << 0; + + *pu32Data = u32Data; + psBuffer->u32Offset += 4; + + // Return success + return TRUE; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteMessageHeaderIntoBuffer + * + * DESCRIPTION: + * Writes a message header into a buffer + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteMessageHeaderIntoBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsMsg *psMsg) +{ + + bool_t bOk = TRUE; + + // First part of the message so ensure the buffer offset is set to zero + psBuffer->u32Offset = 0; + + + // Service ID + bOk &= ORLACO_bWriteU16(psBuffer, psMsg->u16ServiceID); + + // Method ID + bOk &= ORLACO_bWriteU16(psBuffer, psMsg->u16MethodID); + + // Length + bOk &= ORLACO_bWriteU32(psBuffer, psMsg->u32Length); + + // Client ID + bOk &= ORLACO_bWriteU16(psBuffer, psMsg->u16ClientID); + + // Session ID + bOk &= ORLACO_bWriteU16(psBuffer, psMsg->u16SessionID); + + // SOME/IP Version + bOk &= ORLACO_bWriteU8(psBuffer, psMsg->u8SomeIPVersion); + + // Interface Version + bOk &= ORLACO_bWriteU8(psBuffer, psMsg->u8InterfaceVersion); + + // Message type + bOk &= ORLACO_bWriteU8(psBuffer, psMsg->u8MessageType); + + // Return code + bOk &= ORLACO_bWriteU8(psBuffer, psMsg->u8ReturnCode); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_vPrintBuffer + * + * DESCRIPTION: + * Prints the contents of a buffer to the console + * + * RETURNS: + * void + * + ****************************************************************************/ +static void ORLACO_vPrintBuffer(ORLACO_tsBuffer *psBuffer) +{ + for(int n = 0; n < psBuffer->u32Offset; n++) + { + printf(" %02x", psBuffer->pu8Data[n]); + } +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadMessageHeaderFromBuffer + * + * DESCRIPTION: + * Reads a message header from a buffer + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadMessageHeaderFromBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsMsg *psMsg) +{ + + bool_t bOk = TRUE; + + // Service ID + bOk &= ORLACO_bReadU16(psBuffer, &psMsg->u16ServiceID); + + // Method ID + bOk &= ORLACO_bReadU16(psBuffer, &psMsg->u16MethodID); + + // Length + bOk &= ORLACO_bReadU32(psBuffer, &psMsg->u32Length); + + // There should be at least 8 bytes in the rest of the header, exit if not + if(psMsg->u32Length < 8) + { + printf("Not enough length (%d)!\n", psMsg->u32Length); + return FALSE; + } + + // Client ID + bOk &= ORLACO_bReadU16(psBuffer, &psMsg->u16ClientID); + + // Session ID + bOk &= ORLACO_bReadU16(psBuffer, &psMsg->u16SessionID); + + // SOME/IP Version + bOk &= ORLACO_bReadU8(psBuffer, &psMsg->u8SomeIPVersion); + + // Interface Version + bOk &= ORLACO_bReadU8(psBuffer, &psMsg->u8InterfaceVersion); + + // Message type + bOk &= ORLACO_bReadU8(psBuffer, &psMsg->u8MessageType); + + // Return code + bOk &= ORLACO_bReadU8(psBuffer, &psMsg->u8ReturnCode); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_bWriteServiceDiscoveryServiceEntryIntoBuffer + * + * DESCRIPTION: + * Writes a service discovery service entry into a buffer + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bWriteServiceDiscoveryServiceEntryIntoBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsServiceDiscoveryServiceEntry *psServiceEntry) +{ + + bool_t bOk = TRUE; + + bOk &= ORLACO_bWriteU8(psBuffer, psServiceEntry->u8Type); + bOk &= ORLACO_bWriteU8(psBuffer, psServiceEntry->u8Index1stOptions); + bOk &= ORLACO_bWriteU8(psBuffer, psServiceEntry->u8Index2ndOptions); + bOk &= ORLACO_bWriteU8(psBuffer, psServiceEntry->u8NumberOfOptions); + bOk &= ORLACO_bWriteU16(psBuffer, psServiceEntry->u16ServiceID); + bOk &= ORLACO_bWriteU16(psBuffer, psServiceEntry->u16InstanceID); + bOk &= ORLACO_bWriteU8(psBuffer, psServiceEntry->u8MajorVersion); + bOk &= ORLACO_bWriteU24(psBuffer, psServiceEntry->u24TTL); + bOk &= ORLACO_bWriteU32(psBuffer, psServiceEntry->u32MinorVersion); + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReadServiceDiscoveryServiceEntryFromBuffer + * + * DESCRIPTION: + * Reads a service discovery service entry from a buffer + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReadServiceDiscoveryServiceEntryFromBuffer(ORLACO_tsBuffer *psBuffer, ORLACO_tsServiceDiscoveryServiceEntry *psServiceEntry) +{ + + bool_t bOk = TRUE; + + bOk &= ORLACO_bReadU8(psBuffer, &psServiceEntry->u8Type); + bOk &= ORLACO_bReadU8(psBuffer, &psServiceEntry->u8Index1stOptions); + bOk &= ORLACO_bReadU8(psBuffer, &psServiceEntry->u8Index2ndOptions); + bOk &= ORLACO_bReadU8(psBuffer, &psServiceEntry->u8NumberOfOptions); + bOk &= ORLACO_bReadU16(psBuffer, &psServiceEntry->u16ServiceID); + bOk &= ORLACO_bReadU16(psBuffer, &psServiceEntry->u16InstanceID); + bOk &= ORLACO_bReadU8(psBuffer, &psServiceEntry->u8MajorVersion); + bOk &= ORLACO_bReadU24(psBuffer, &psServiceEntry->u24TTL); + bOk &= ORLACO_bReadU32(psBuffer, &psServiceEntry->u32MinorVersion); + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bSendDatagram + * + * DESCRIPTION: + * Sends the contents of the specified buffer as a UDP datagram to the specified IP address + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bSendDatagram(UDPSOCKET sktTx, struct sockaddr_in *psDstAddr, ORLACO_tsBuffer *psBuffer) +{ + bool_t bOk = TRUE; + int iLen; + // printf("Tx: "); + // ORLACO_vPrintBuffer(psBuffer); + // printf("\n"); + + iLen = sendto(sktTx, (const char *)psBuffer->pu8Data, psBuffer->u32Offset, 0, (const struct sockaddr*)psDstAddr, sizeof(struct sockaddr_in)); + if(iLen != psBuffer->u32Offset) + { + printf("Sendto returned %d\n", iLen); + bOk &= FALSE; + } + + // Buffer no longer required so free it here + ORLACO_vBufferDestroy(psBuffer); + + return bOk; +} + + +/**************************************************************************** + * + * NAME: ORLACO_bReceiveDatagram + * + * DESCRIPTION: + * Receives a message + * + * RETURNS: + * bool_t - TRUE if successful, FALSE otherwise + * + ****************************************************************************/ +static bool_t ORLACO_bReceiveDatagram(ORLACO_tsInstance *psInstance, ORLACO_tsMsg *psRxMsg, uint16_t u16MethodID) +{ + bool_t bOk = TRUE; + int iTimeout = ORLACO_MAX_RESPONSE_TIME_MS; + int iLen = 0; + + uint16_t u16SenderPort; + int64_t i64BytesReceived = 0; + + const struct sockaddr_in sRxAddr = {}; + int iRxAddrLen = sizeof(sRxAddr); + + memset(psRxMsg, 0, sizeof(ORLACO_tsMsg)); + + // Allocate a buffer + ORLACO_tsBuffer *psBuffer = ORLACO_psBufferCreate(ORLACO_BUFFER_LENGTH); + if(psBuffer == NULL) + { + printf("Error: Buffer allocation failed in %s\n", __FUNCTION__); + return FALSE; + } + + do + { + + while((iLen = recvfrom(psInstance->Socket, (char*)psBuffer->pu8Data, psBuffer->u32Length, 0, (struct sockaddr*)&sRxAddr, &iRxAddrLen)) <= 0) + { + // if(iLen == SOCKET_ERROR) + // { + // printf("Last error = %d, ptr = %p\n",WSAGetLastError(), sRxBuffer.pu8Data); + // // return FALSE; + // } + iTimeout -= ORLACO_SOCKET_READ_TIMEOUT_MS; + if(iTimeout <= 0) + { + if(psInstance->eVerbosity >= E_ORLACO_VERBOSITY_DEBUG) printf("Rx Timeout\n"); + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } + + // printf("Addr: %s\n", inet_ntoa(sRxAddr.sin_addr)); + + psBuffer->u32Length = (uint32_t)iLen; + + // psBuffer->u32Offset = (uint32_t)iLen; + // printf("Rx: "); + // ORLACO_vPrintBuffer(psBuffer); + // printf("\n"); + + // Get the IP address of the sender +#ifdef _WIN32 + psRxMsg->uSrcAddr.au8IP[3] = sRxAddr.sin_addr.S_un.S_un_b.s_b1; + psRxMsg->uSrcAddr.au8IP[2] = sRxAddr.sin_addr.S_un.S_un_b.s_b2; + psRxMsg->uSrcAddr.au8IP[1] = sRxAddr.sin_addr.S_un.S_un_b.s_b3; + psRxMsg->uSrcAddr.au8IP[0] = sRxAddr.sin_addr.S_un.S_un_b.s_b4; +#else + psRxMsg->uSrcAddr.au8IP[3] = (uint8_t)((sRxAddr.sin_addr.s_addr >> 0) & 0xff); + psRxMsg->uSrcAddr.au8IP[2] = (uint8_t)((sRxAddr.sin_addr.s_addr >> 8) & 0xff); + psRxMsg->uSrcAddr.au8IP[1] = (uint8_t)((sRxAddr.sin_addr.s_addr >> 16) & 0xff); + psRxMsg->uSrcAddr.au8IP[0] = (uint8_t)((sRxAddr.sin_addr.s_addr >> 24) & 0xff); +#endif + + psBuffer->u32Offset = 0; + + bOk = ORLACO_bReadMessageHeaderFromBuffer(psBuffer, psRxMsg); + if(!bOk){ + printf("Error reading message header\n"); + continue; + } + + // printf("RX: ServiceID=%04x MethodID=%04x Length=%08x ClientID=%04x SessionID=%04x SOME/IP Version=%d Interface Version=%d MessageType=%02x ReturnCode=%02x\n", + // psRxMsg->u16ServiceID, + // psRxMsg->u16MethodID, + // psRxMsg->u32Length, + // psRxMsg->u16ClientID, + // psRxMsg->u16SessionID, + // psRxMsg->u8SomeIPVersion, + // psRxMsg->u8InterfaceVersion, + // psRxMsg->u8MessageType, + // psRxMsg->u8ReturnCode); + } + while((psRxMsg->u16ServiceID != psInstance->u16ServiceID)&&(psRxMsg->u16MethodID != u16MethodID)&&(!bOk)); + + // Check the response code + if(psRxMsg->u8ReturnCode != E_ORLACO_RETURN_CODE_OK) + { + printf("Error: Response code = %d: %s\n", psRxMsg->u8ReturnCode, ORLACO_pcGetReturnCodeAsString((ORLACO_teReturnCode)psRxMsg->u8ReturnCode)); + ORLACO_vBufferDestroy(psBuffer); + return FALSE; + } + + // printf("Rx Success\n"); + + switch(psRxMsg->u16MethodID) + { + + case E_ORLACO_METHOD_ID_GET_DATA_SHEET: + break; + + case E_ORLACO_METHOD_ID_GET_CAM_STATUS: + break; + + case E_ORLACO_METHOD_ID_GET_HOST_PARAMETERS: + break; + + case E_ORLACO_METHOD_ID_GET_REGION_OF_INTEREST: + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16P1X); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16P1Y); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16P2X); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16P2Y); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown1SetTo0x01); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown2SetTo0x00); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16Unknown3SetTo0x0000); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16OutputWidth); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16OutputHeight); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown4SetTo0x00); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8FrameRate); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16Unknown4bSetTo0x0000); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown5SetTo0x00); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown6SetTo0x02); + bOk &= ORLACO_bReadU32(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u32MaxBitrate); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8VideoCompressionMode); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown7SetTo0x00); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown8SetTo0x00); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown9SetTo0x00); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown10SetTo0x04); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u8Unknown11SetTo0x01); + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegionOfInterestResponsePayload.u16Unknown12SetTo0x00ff); + break; + + case E_ORLACO_METHOD_ID_GET_REGIONS_OF_INTEREST: + break; + + case E_ORLACO_METHOD_ID_GET_VIDEO_FORMAT: + break; + + case E_ORLACO_METHOD_ID_GET_HISTOGRAMM_FORMAT: + break; + + case E_ORLACO_METHOD_ID_GET_CAM_CONTROL: + break; + + case E_ORLACO_METHOD_ID_GET_CAM_CONTROLS: + break; + + case E_ORLACO_METHOD_ID_GET_CAM_REGISTER: + break; + + case E_ORLACO_METHOD_ID_GET_CAM_REGISTERS: + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegistersResponsePayload.u16Qtty); + for(int n = 0; n < psRxMsg->uPayload.sGetRegistersResponsePayload.u16Qtty; n++) + { + bOk &= ORLACO_bReadU16(psBuffer, &psRxMsg->uPayload.sGetRegistersResponsePayload.asRegisterValues[n].u16Address); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegistersResponsePayload.asRegisterValues[n].u8Padding); + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sGetRegistersResponsePayload.asRegisterValues[n].u8Value); + } + break; + + case E_ORLACO_METHOD_ID_SERVICE_DISCOVERY: + bOk &= ORLACO_bReadU8(psBuffer, &psRxMsg->uPayload.sServiceDiscoveryPayload.u8Flags); + bOk &= ORLACO_bReadU24(psBuffer, &psRxMsg->uPayload.sServiceDiscoveryPayload.u24Reserved); + bOk &= ORLACO_bReadU32(psBuffer, &psRxMsg->uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes); + for(int n = 0; n < psRxMsg->uPayload.sServiceDiscoveryPayload.u32LengthOfEntriesArrayInBytes; n += ORLACO_SD_OPTION_LENGTH) + { + bOk &= ORLACO_bReadServiceDiscoveryServiceEntryFromBuffer(psBuffer, &psRxMsg->uPayload.sServiceDiscoveryPayload.asServiceEntry[n]); + } + break; + + // Either the response doesn't contain any data or no idea what it is + default: + break; + + } + + ORLACO_vBufferDestroy(psBuffer); + + return bOk; + +} + + +/**************************************************************************** + * + * NAME: ORLACO_pcGetReturnCodeAsString + * + * DESCRIPTION: + * Receives a string describing the given response code + * + * RETURNS: + * char * - A pointer to the text representation of the response code + * + ****************************************************************************/ +static char *ORLACO_pcGetReturnCodeAsString(ORLACO_teReturnCode eReturnCode) +{ + typedef struct { + ORLACO_teReturnCode eCode; + char *pcText; + } tsLookup; + + int n; + + const tsLookup asLookup[] = { + {E_ORLACO_RETURN_CODE_OK , "Ok"}, + {E_ORLACO_RETURN_CODE_NOT_OK , "An unspecified error occurred"}, + {E_ORLACO_RETURN_CODE_UNKNOWN_SERVICE , "The requested Service ID is unknown."}, + {E_ORLACO_RETURN_CODE_UNKNOWN_METHOD , "The requested Method ID is unknown. Service ID is known."}, + {E_ORLACO_RETURN_CODE_NOT_READY , "Service ID and Method ID are known. Application not running."}, + {E_ORLACO_RETURN_CODE_NOT_REACHABLE , "System running the service is not reachable (internal error code only)."}, + {E_ORLACO_RETURN_CODE_TIMEOUT , "A timeout occurred (internal error code only)."}, + {E_ORLACO_RETURN_CODE_WRONG_PROTOCOL_VERSION , "Version of SOME/IP protocol not supported"}, + {E_ORLACO_RETURN_CODE_WRONG_INTERFACE_VERSION , "Interface version mismatch"}, + {E_ORLACO_RETURN_CODE_MALFORMED_MESSAGE , "Deserialization error, so that payload cannot be deserialized."}, + {E_ORLACO_RETURN_CODE_WRONG_MESSAGE_TYPE , "An unexpected message type was received (e.g. REQUEST_NO_RETURN for a method defined as REQUEST.)"}, + {E_ORLACO_RETURN_CODE_LOCKED_BY_FOREIGN_INSTANCE, "Camera service is already locked by another client"}, + {E_ORLACO_RETURN_CODE_LOCK_EXPIRED , "The camera lock has expired"}, + {E_ORLACO_RETURN_CODE_NOT_LOCKED , "Camera is not locked"}, + {E_ORLACO_RETURN_CODE_INVALID_PS_ENTRY , "The requested PSE ID is unknown"}, + {E_ORLACO_RETURN_CODE_INVALID_PS_OPERATION , "The requested PSE operation is not allowed, e.g. store on a RO PSE"}, + {E_ORLACO_RETURN_CODE_INVALID_PS_DATA , "The PSE contains a CRC16 error"}, + {E_ORLACO_RETURN_CODE_NO_MORE_SPACE , "No more space available to store the PSE"}, + {E_ORLACO_RETURN_CODE_INVALID_ROI_INDEX , "The requested ROI is out of range"}, + {E_ORLACO_RETURN_CODE_INVALID_ROI_NUMBER , "The requested number of ROIs is out of range, defined by sDatasheet.numOfRegionOfInterest"}, + {E_ORLACO_RETURN_CODE_INVALID_VIDEO_FORMAT , "Invalid value in video format"}, + {E_ORLACO_RETURN_CODE_INVALID_HISTOGRAM_FORMAT , "Invalid value in histogram format"}, + {E_ORLACO_RETURN_CODE_INVALID_CONTROL_INDEX , "The requested camControlIndex is out of range or not supported by the camera"}, + {E_ORLACO_RETURN_CODE_INVALID_CONTROL_MODE , "The requested control mode is not supported by the camera"}, + {E_ORLACO_RETURN_CODE_INVALID_CONTROL_VALUE , "The requested control value is out of the range, defined in sCamControl"}, + {E_ORLACO_RETURN_CODE_INVALID_REGISTER_ADDRESS , "The register address is not supported by the imager"}, + {E_ORLACO_RETURN_CODE_INVALID_REGISTER_VALUE , "The value for the given register address is not supported by the imager"}, + {E_ORLACO_RETURN_CODE_INVALID_REGISTER_OPERATION, "The operation requested is invalid for this register"}, + }; + for(n = 0; n < sizeof(asLookup) / sizeof(tsLookup); n++) + { + if(asLookup[n].eCode == eReturnCode) return asLookup[n].pcText; + } + + return "Unknown return code"; +} + +bool_t ORLACO_bIPAlreadyInArray(ORLACO_tsInstance *psInstance, ORLACO_tuIP IP) +{ + int n; + for(n = 0; n < psInstance->u16NumCameras; n++) + { + if(psInstance->psCameras[n].uIP.u32IP == IP.u32IP) + { + return TRUE; + } + } + return FALSE; +} + + + +/****************************************************************************/ +/*** END OF FILE ***/ +/****************************************************************************/ diff --git a/orlaco.h b/orlaco.h new file mode 100644 index 0000000..8ac2a61 --- /dev/null +++ b/orlaco.h @@ -0,0 +1,296 @@ +#ifndef ORLACO_H +#define ORLACO_H + +/****************************************************************************/ +/*** Include files ***/ +/****************************************************************************/ + +#include +#include + +// https://stackoverflow.com/questions/28027937/cross-platform-sockets + +#ifdef _WIN32 + /* See http://stackoverflow.com/questions/12765743/getaddrinfo-on-win32 */ + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 /* Windows XP. */ + #endif + #include + #include +#else + /* Assume that any non-Windows platform uses POSIX-style sockets instead. */ + #include + #include + #include /* Needed for getaddrinfo() and freeaddrinfo() */ + #include /* Needed for close() */ +#endif + +#include "common.h" + +/****************************************************************************/ +/*** Macro Definitions ***/ +/****************************************************************************/ + +#define ORLACO_DEFAULT_PORT 17215 +#define ORLACO_MAX_CAMERAS 10 +#define ORLACO_MAX_REGISTERS 100 +#define ORLACO_MAX_SD_SERVICES 10 +#define ORLACO_MAX_SD_OPTIONS 2 +#define ORLACO_MAX_RESPONSE_TIME_MS 5000 + +#define ORLACO_SD_OPTION_LENGTH 0x10 + +#define ORLACO_BUFFER_LENGTH 1500 +#define ORLACO_NUM_REGIONS_OF_INTEREST 11 + +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +/****************************************************************************/ +/*** Type Definitions ***/ +/****************************************************************************/ + +typedef enum { + E_ORLACO_VERBOSITY_ERRORS_ONLY = 0, + E_ORLACO_VERBOSITY_INFO, + E_ORLACO_VERBOSITY_DEBUG, +} ORLACO_eVerbosityLevel; + +typedef enum { + E_ORLACO_RETURN_CODE_OK = 0x00, // No error occurred + E_ORLACO_RETURN_CODE_NOT_OK = 0x01, // An unspecified error occurred + E_ORLACO_RETURN_CODE_UNKNOWN_SERVICE = 0x02, // The requested Service ID is unknown. + E_ORLACO_RETURN_CODE_UNKNOWN_METHOD = 0x03, // The requested Method ID is unknown. Service ID is known. + E_ORLACO_RETURN_CODE_NOT_READY = 0x04, // Service ID and Method ID are known. Application not running. + E_ORLACO_RETURN_CODE_NOT_REACHABLE = 0x05, // System running the service is not reachable (internal error code only). + E_ORLACO_RETURN_CODE_TIMEOUT = 0x06, // A timeout occurred (internal error code only). + E_ORLACO_RETURN_CODE_WRONG_PROTOCOL_VERSION = 0x07, // Version of SOME/IP protocol not supported + E_ORLACO_RETURN_CODE_WRONG_INTERFACE_VERSION = 0x08, // Interface version mismatch + E_ORLACO_RETURN_CODE_MALFORMED_MESSAGE = 0x09, // Deserialization error, so that payload cannot be deserialized. + E_ORLACO_RETURN_CODE_WRONG_MESSAGE_TYPE = 0x0a, // An unexpected message type was received (e.g. REQUEST_NO_RETURN for a method defined as REQUEST.) + + E_ORLACO_RETURN_CODE_LOCKED_BY_FOREIGN_INSTANCE = 0x20, // Camera service is already locked by another client + E_ORLACO_RETURN_CODE_LOCK_EXPIRED = 0x21, // The camera lock has expired + E_ORLACO_RETURN_CODE_NOT_LOCKED = 0x22, // Camera is not locked + E_ORLACO_RETURN_CODE_INVALID_PS_ENTRY = 0x24, // The requested PSE ID is unknown + E_ORLACO_RETURN_CODE_INVALID_PS_OPERATION = 0x25, // The requested PSE operation is not allowed, e.g. store on a RO PSE + E_ORLACO_RETURN_CODE_INVALID_PS_DATA = 0x26, // The PSE contains a CRC16 error + E_ORLACO_RETURN_CODE_NO_MORE_SPACE = 0x27, // No more space available to store the PSE + E_ORLACO_RETURN_CODE_INVALID_ROI_INDEX = 0x30, // The requested ROI is out of range + E_ORLACO_RETURN_CODE_INVALID_ROI_NUMBER = 0x31, // The requested number of ROIs is out of range, defined by sDatasheet.numOfRegionOfInterest + E_ORLACO_RETURN_CODE_INVALID_VIDEO_FORMAT = 0x32, // Invalid value in video format + E_ORLACO_RETURN_CODE_INVALID_HISTOGRAM_FORMAT = 0x33, // Invalid value in histogram format + E_ORLACO_RETURN_CODE_INVALID_CONTROL_INDEX = 0x35, // The requested camControlIndex is out of range or not supported by the camera + E_ORLACO_RETURN_CODE_INVALID_CONTROL_MODE = 0x36, // The requested control mode is not supported by the camera + E_ORLACO_RETURN_CODE_INVALID_CONTROL_VALUE = 0x37, // The requested control value is out of the range, defined in sCamControl + E_ORLACO_RETURN_CODE_INVALID_REGISTER_ADDRESS = 0x38, // The register address is not supported by the imager + E_ORLACO_RETURN_CODE_INVALID_REGISTER_VALUE = 0x39, // The value for the given register address is not supported by the imager + E_ORLACO_RETURN_CODE_INVALID_REGISTER_OPERATION = 0x3A +} ORLACO_teReturnCode; + + +// Must make sure these are in the same order as the register addresses +typedef enum { + E_ORLACO_REGISTER_INDEX_LED_MODE = 0, + E_ORLACO_REGISTER_INDEX_STREAM_PROTOCOL, + E_ORLACO_REGISTER_INDEX_STATIC_IP_ADDRESS_0, + E_ORLACO_REGISTER_INDEX_STATIC_IP_ADDRESS_1, + E_ORLACO_REGISTER_INDEX_STATIC_IP_ADDRESS_2, + E_ORLACO_REGISTER_INDEX_STATIC_IP_ADDRESS_3, + E_ORLACO_REGISTER_INDEX_STATIC_NETWORK_MASK_0, + E_ORLACO_REGISTER_INDEX_STATIC_NETWORK_MASK_1, + E_ORLACO_REGISTER_INDEX_STATIC_NETWORK_MASK_2, + E_ORLACO_REGISTER_INDEX_STATIC_NETWORK_MASK_3, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_0, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_1, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_2, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_3, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_4, + E_ORLACO_REGISTER_INDEX_MAC_ADDRESS_5, + E_ORLACO_REGISTER_INDEX_VLAN_ID_0, + E_ORLACO_REGISTER_INDEX_VLAN_ID_1, + E_ORLACO_REGISTER_INDEX_STREAM_ID_0, + E_ORLACO_REGISTER_INDEX_STREAM_ID_1, + E_ORLACO_REGISTER_INDEX_STREAM_ID_2, + E_ORLACO_REGISTER_INDEX_STREAM_ID_3, + E_ORLACO_REGISTER_INDEX_STREAM_ID_4, + E_ORLACO_REGISTER_INDEX_STREAM_ID_5, + E_ORLACO_REGISTER_INDEX_STREAM_ID_6, + E_ORLACO_REGISTER_INDEX_STREAM_ID_7, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_IP_ADDRESS_0, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_IP_ADDRESS_1, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_IP_ADDRESS_2, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_IP_ADDRESS_3, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_0, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_1, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_2, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_3, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_4, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_MAC_ADDRESS_5, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_PORT_0, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_DESTINATION_PORT_1, + E_ORLACO_REGISTER_INDEX_SELECTED_ROI, + E_ORLACO_REGISTER_INDEX_NO_STREAM_AT_BOOT, + E_ORLACO_REGISTER_INDEX_UDP_COMMUNICATION_PORT_0, + E_ORLACO_REGISTER_INDEX_UDP_COMMUNICATION_PORT_1, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_SOURCE_PORT_0, + E_ORLACO_REGISTER_INDEX_RTP_STREAM_SOURCE_PORT_1, + E_ORLACO_REGISTER_INDEX_HDR, + E_ORLACO_REGISTER_INDEX_OVERLAY, + E_ORLACO_REGISTER_INDEX_DHCP, + E_ORLACO_REGISTER_INDEX_WAIT_FOR_MAC, + E_ORLACO_REGISTER_INDEX_WAIT_FOR_PTP_SYNC, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_0, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_1, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_2, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_3, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_4, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_5, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_6, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_7, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_8, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_9, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_10, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_11, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_12, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_13, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_14, + E_ORLACO_REGISTER_INDEX_DHCP_HOSTNAME_15, +} ORLACO_teRegisterIndex; + +typedef enum { + E_ORLACO_LED_MODE_OFF = 0x0000, + E_ORLACO_LED_MODE_AUTO = 0x0001, + E_ORLACO_LED_MODE_ON = 0x0002, +} ORLACO_teLedMode; + +typedef enum { + E_ORLACO_DHCP_MODE_DISABLED = 0x0000, + E_ORLACO_DHCP_MODE_ENABLED = 0x0001, +} ORLACO_teDHCPMode; + +typedef enum { + E_ORLACO_VIDEO_COMPRESSION_MODE_NONE = 0x00, + E_ORLACO_VIDEO_COMPRESSION_MODE_JPEG = 0x01, + E_ORLACO_VIDEO_COMPRESSION_MODE_H264 = 0x02, +} ORLACO_teVideoCompressionMode; + + +typedef enum { + E_ORLACO_CAMERA_MODE_START_CAMERA_SERVICE = 0x01, // The camera application will be started + E_ORLACO_CAMERA_MODE_STOP_CAMERA_SERVICE = 0x02, // The camera application will be stopped + E_ORLACO_CAMERA_MODE_RESTART_CAMERA_SERVICE = 0x03, // The camera application will be restarted + E_ORLACO_CAMERA_MODE_STOP_CAMERA = 0x04, // The camera will be stopped (standby mode). Requires a power cycle or wake on LAN to recover +} ORLACO_teCameraMode; + +typedef struct { + uint16_t u16Address; + uint8_t u8Padding; + uint8_t u8Value; + char *pcDescription; + char *pcHelp; + bool_t bWrite; + bool_t bRead; +} ORLACO_tsRegisterValue; + + +typedef union { + uint8_t au8IP[4]; + uint32_t u32IP; +} ORLACO_tuIP; + + +typedef struct { + uint16_t u16P1X; + uint16_t u16P1Y; + uint16_t u16P2X; + uint16_t u16P2Y; + uint16_t u16OutputWidth; + uint16_t u16OutputHeight; + uint32_t u32MaxBitrate; // Specified in Megabits per second + uint8_t u8FrameRate; + ORLACO_teVideoCompressionMode eCompressionMode; + bool_t bWrite; + bool_t bRead; +} ORLACO_tsRegionOfInterest; + + +typedef struct { + uint8_t u8Type; + uint8_t u8Index1stOptions; + uint8_t u8Index2ndOptions; + uint8_t u8NumberOfOptions; + uint16_t u16ServiceID; + uint16_t u16InstanceID; + uint8_t u8MajorVersion; + uint32_t u24TTL; + uint32_t u32MinorVersion; +} ORLACO_tsServiceDiscoveryServiceEntry; + + +typedef struct { + ORLACO_tuIP uIP; + ORLACO_tsServiceDiscoveryServiceEntry sDiscoveryServiceEntry; +} ORLACO_tsCamera; + +#ifdef _WIN32 + typedef unsigned int UDPSOCKET; +#else + typedef int UDPSOCKET; +#endif + +typedef struct { + ORLACO_eVerbosityLevel eVerbosity; + struct sockaddr_in fdServer; + struct sockaddr_in fdUnicast; + struct sockaddr_in fdBroadcast; + UDPSOCKET Socket; + uint16_t u16DstPort; + uint16_t u16ServiceID; + uint16_t u16ClientID; + uint16_t u16SessionID; + uint16_t u16ResponseTimer; + ORLACO_teCameraMode eCameraMode; + uint16_t u16NumRegisters; + ORLACO_tsRegisterValue *psRegisters; + uint16_t u16NumRegionsOfInterest; + ORLACO_tsRegionOfInterest *psRegionsOfInterest; + uint16_t u16NumCameras; + ORLACO_tsCamera *psCameras; +} ORLACO_tsInstance; + +/****************************************************************************/ +/*** Exported Functions ***/ +/****************************************************************************/ + +bool_t ORLACO_bInit(ORLACO_tsInstance *psInstance, char *pcUnicastIP, char *pcBroadcastIP, uint16_t u16DstPort); +void ORLACO_vDeInit(ORLACO_tsInstance *psInstance); +void ORLACO_vSetVerbosity(ORLACO_tsInstance *psInstance, ORLACO_eVerbosityLevel eVerbosityLevel); +bool_t ORLACO_bSetBroadcastIP(ORLACO_tsInstance *psInstance, char *pcIpAddress, uint16_t u16DstPort); +bool_t ORLACO_bSetUnicastIP(ORLACO_tsInstance *psInstance, char *pcIpAddress, uint16_t u16DstPort); + +// bool_t ORLACO_bBufferTest(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bDiscover(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bSetCamExclusive(ORLACO_tsInstance *psInstance, uint32_t u32ExclusiveTime); +bool_t ORLACO_bEraseCamExclusive(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bSetCamMode(ORLACO_tsInstance *psInstance, ORLACO_teCameraMode eMode); +bool_t ORLACO_bGetRegisters(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bSetRegisters(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bGetAllRegisters(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bGetRegionOfInterest(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterest, ORLACO_tsRegionOfInterest *psRegionOfInterest); +bool_t ORLACO_bGetRegionsOfInterest(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bSetRegionOfInterest(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterestIndex, ORLACO_tsRegionOfInterest *psRegionOfInterest); +bool_t ORLACO_bSetRegionsOfInterest(ORLACO_tsInstance *psInstance); +bool_t ORLACO_bSubscribeRoiVideo(ORLACO_tsInstance *psInstance, uint32_t u32RegionOfInterest); + + +#endif // ORLACO_H + +/****************************************************************************/ +/*** END OF FILE ***/ +/****************************************************************************/