diff options
author | Christian Daniel <cd@maintech.de> | 2012-03-05 18:48:13 +0100 |
---|---|---|
committer | Christian Daniel <cd@maintech.de> | 2012-03-05 18:48:13 +0100 |
commit | 956debafb88a59f83affc27888834d3fa627f723 (patch) | |
tree | 8b6d8ff263eeb6ad2e731f9d5481d5b38e15b053 | |
parent | bd2b9a96979a82f5022c69453907c102e99185f8 (diff) |
add sweep tool (Linux only)
-rw-r--r-- | utils/osmosdr-sweep/Makefile | 85 | ||||
-rw-r--r-- | utils/osmosdr-sweep/portaudio.diff | 12 | ||||
-rw-r--r-- | utils/osmosdr-sweep/src/main.c | 234 | ||||
-rw-r--r-- | utils/osmosdr-sweep/src/serial.c | 338 | ||||
-rw-r--r-- | utils/osmosdr-sweep/src/serial.h | 16 | ||||
-rw-r--r-- | utils/osmosdr-sweep/src/utils.c | 11 | ||||
-rw-r--r-- | utils/osmosdr-sweep/src/utils.h | 11 |
7 files changed, 707 insertions, 0 deletions
diff --git a/utils/osmosdr-sweep/Makefile b/utils/osmosdr-sweep/Makefile new file mode 100644 index 0000000..223b164 --- /dev/null +++ b/utils/osmosdr-sweep/Makefile @@ -0,0 +1,85 @@ +# binary file name +TARGET=osdr-sweep + +# CROSS_COMPILE= +QUIET=@ + +# N = build release version +# y = optimize but include debugger info +# Y = build debug version +DEBUG=N + +C_SOURCES=\ + src/main.c \ + src/serial.c \ + src/utils.c + +# general compiler flags +CFLAGS=-Wall +LDFLAGS=-Wl,-Map=$(FULLTARGET).map +LIBS=-lrt libportaudio.a -lasound -lm -lpthread + +############################################################################## + +SUBDIRS=$(sort $(dir $(C_SOURCES))) + +DEPDIR=deps +OBJDIR=objs +BINDIR=bin +DEPDIRS=$(addprefix $(DEPDIR)/,$(SUBDIRS)) +OBJDIRS=$(addprefix $(OBJDIR)/,$(SUBDIRS)) + +CC=$(CROSS_COMPILE)gcc +LD=$(CROSS_COMPILE)gcc + +COBJS=$(C_SOURCES:%.c=%.o) +DEPS=$(C_SOURCES:%.c=%.dep) +FULLDEPS=$(addprefix $(DEPDIR)/,$(DEPS)) +FULLCOBJS=$(addprefix $(OBJDIR)/,$(COBJS)) +FULLTARGET=$(addprefix $(BINDIR)/,$(TARGET)) + +ifeq ($(DEBUG),Y) + # debug version + CFLAGS+=-O0 -g3 + LDFLAGS+=-g3 +else ifeq ($(DEBUG),y) + # optimized version with debugger info + CFLAGS+=-O2 -g3 -Werror -ffunction-sections -fdata-sections + LDFLAGS+=-g3 -Wl,--gc-sections +else + # release version + CFLAGS+=-O2 -s -Werror -ffunction-sections -fdata-sections + LDFLAGS+=-s -Wl,--gc-sections +endif + +.PHONY: all build clean distclean + +all: build + +build: $(FULLTARGET) + +-include $(FULLDEPS) + +$(FULLTARGET): $(DEPDIRS) $(OBJDIRS) $(BINDIR) $(FULLCOBJS) + @echo LD \ $(TARGET) + $(QUIET)$(LD) $(LDFLAGS) -o $(FULLTARGET) -Wl,--start-group $(FULLCOBJS) $(LIBS) -Wl,--end-group + $(QUIET)ln -sf $(FULLTARGET) $(TARGET) + +$(FULLCOBJS): + @echo C\ \ \ $(patsubst $(OBJDIR)/%,%,$(patsubst %.o,%.c, $@)) + $(QUIET)$(CC) $(CFLAGS) $(CFLAGS_$(subst /,_,$(patsubst %.o,%,$@))) -MD -MP -MF $(patsubst %.o,$(DEPDIR)/%.dep,$(patsubst $(OBJDIR)/%,%,$@)) -c $(patsubst $(OBJDIR)/%,%,$(patsubst %.o,%.c, $@)) -o $@ + +$(DEPDIRS): + $(QUIET)mkdir -p $@ + +$(OBJDIRS): + $(QUIET)mkdir -p $@ + +$(BINDIR): + $(QUIET)mkdir -p $@ + +clean: + $(QUIET)echo CLEAN + $(QUIET)rm -Rf $(DEPDIR) $(OBJDIR) $(BINDIR) $(TARGET) $(TARGET).map *~ *.s *.ss + +distclean: clean diff --git a/utils/osmosdr-sweep/portaudio.diff b/utils/osmosdr-sweep/portaudio.diff new file mode 100644 index 0000000..15bd249 --- /dev/null +++ b/utils/osmosdr-sweep/portaudio.diff @@ -0,0 +1,12 @@ +diff -ur portaudio/src/common/pa_front.c portaudio-osmosdr/src/common/pa_front.c +--- portaudio/src/common/pa_front.c 2011-08-18 05:43:51.000000000 +0200 ++++ portaudio-osmosdr/src/common/pa_front.c 2012-02-29 18:29:40.100609445 +0100 +@@ -965,7 +965,7 @@ + + + /* Check for absurd sample rates. */ +- if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) ++ if( (sampleRate < 1000.0) || (sampleRate > 1500000.0) ) + return paInvalidSampleRate; + + if( ((streamFlags & ~paPlatformSpecificFlags) & ~(paClipOff | paDitherOff | paNeverDropInput | paPrimeOutputBuffersUsingStreamCallback ) ) != 0 ) diff --git a/utils/osmosdr-sweep/src/main.c b/utils/osmosdr-sweep/src/main.c new file mode 100644 index 0000000..774da4b --- /dev/null +++ b/utils/osmosdr-sweep/src/main.c @@ -0,0 +1,234 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <math.h> +#include <portaudio.h> +#include "serial.h" +#include "utils.h" + +struct SweepState { + int serialFd; + int settleTime; + int sampleTime; + uint64_t freq; + double acc; + double num; + double start; + double stop; +}; + +static int printSyntax() +{ + fprintf(stderr, "Error: Invalid command line!\n\n" + "syntax: sdr-sweep /dev/ttyUSB0 start stop" + " - start = start frequency in MHz, e.g 88.5\n" + " - stop = stop frequency in MHz, e.g. 108\n" + "frequency range of OsmoSDR is 64MHz - 1700MHz\n"); + + return EXIT_FAILURE; +} + +static void setFreq(int fd, uint64_t freq) +{ + char str[128]; + char* c; + char str2[2] = { '\0', '\0' }; + + while(serialGetS(fd, str, sizeof(str), 1) >= 0) + ; + + sprintf(str, "tuner.freq=%llu\r", freq); + + for(c = str; *c != '\0'; c++) { + serialPutC(fd, *c); + if(*c == '\r') + str2[0] = '\n'; + else str2[0] = *c; + if(serialExpect(fd, str2, 1) < 0) { + serialPutC(fd, *c); + if(serialExpect(fd, str2, 1) < 0) { + fprintf(stderr, "serial port broken (%d = %c)\n", *c, *c); + return; + } + } + } +} + +static int audioCallback(const void* inputBuffer, void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) +{ + struct SweepState* state = (struct SweepState*)userData; + int i; + const int16_t* input = (const int16_t*)inputBuffer; + uint8_t c; + double v; + int max; + + //fprintf(stderr, "block %lu\n", framesPerBuffer); + + while(serialGetC(state->serialFd, &c, 0) >= 0) + //fprintf(stderr, "%c", c); + ; + + if(state->settleTime > 0) { + state->settleTime -= framesPerBuffer; + if(state->settleTime <= 0) { + state->sampleTime = 100000; + state->acc = 0.0; + state->num = 0.0; + } + return 0; + } + + if(state->sampleTime > 0) { + max = 0; + for(i = 0; i < framesPerBuffer; i++) { + if(abs(*input) > max) + max = abs(*input); + v = *input / 32768.0; + state->acc += v * v; + input++; + if(abs(*input) > max) + max = abs(*input); + v = *input / 32768.0; + state->acc += v * v; + input++; + state->num += 2; + } + //fprintf(stderr, "-> %d\n", max); + + state->sampleTime -= framesPerBuffer; + if(state->sampleTime <= 0) { + printf("%.1f\t%.1f\n", state->freq / 1000000.0, 20.0 * log10(sqrt(state->acc / state->num))); + fflush(stdout); + state->freq += 100000; + setFreq(state->serialFd, state->freq); + state->settleTime = 500000; + } + } + + return 0; +} + +static PaStream* audioOpen(struct SweepState* state) +{ + int numDevices; + const PaDeviceInfo* deviceInfo; + PaError err; + int i; + int dev = -1; + PaStream* stream = NULL; + PaStreamParameters inputParameters; + + err = Pa_Initialize(); + if(err != paNoError) + goto error_noinit; + + numDevices = Pa_GetDeviceCount(); + if(numDevices < 0) { + err = numDevices; + goto error; + } + + for(i = 0; i < numDevices; i++) { + deviceInfo = Pa_GetDeviceInfo(i); + fprintf(stderr, "#%02d -> [%s]\n", i, deviceInfo->name); + if(strncmp(deviceInfo->name, "OsmoSDR", 7) == 0) + dev = i; + } + if(dev < 0) { + fprintf(stderr, "OsmoSDR not found!\n"); + Pa_Terminate(); + return NULL; + } + + fprintf(stderr, "Using device #%02d (sample rate %.0f Hz)\n", dev, Pa_GetDeviceInfo(dev)->defaultSampleRate); + + bzero(&inputParameters, sizeof(inputParameters)); + inputParameters.channelCount = 2; + inputParameters.device = dev; + inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev)->defaultHighInputLatency ; + inputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream(&stream, &inputParameters, NULL, Pa_GetDeviceInfo(dev)->defaultSampleRate, 20000, + paClipOff | paDitherOff, audioCallback, state); + if(err != paNoError) + goto error_noinit; + + err = Pa_StartStream(stream); + if(err != paNoError) + goto error_noinit; + + return stream; + +error: + Pa_Terminate(); + +error_noinit: + fprintf(stderr, "PortAudio error: %s\n", Pa_GetErrorText(err)); + return NULL; +} + +static void audioClose(PaStream* stream) +{ + PaError err; + + err = Pa_StopStream(stream); + if(err != paNoError) + fprintf(stderr, "PortAudio error: %s\n", Pa_GetErrorText(err)); + + err = Pa_CloseStream(stream); + if(err != paNoError) + fprintf(stderr, "PortAudio error: %s\n", Pa_GetErrorText(err)); + + err = Pa_Terminate(); + if(err != paNoError) + fprintf(stderr, "PortAudio error: %s\n", Pa_GetErrorText(err)); +} + +int main(int argc, char* argv[]) +{ + HANDLE fd; + PaStream* stream; + struct SweepState state; + + if(argc < 4) + return printSyntax(); + + if((fd = serialOpen(argv[1])) == INVALID_HANDLE_VALUE) + return EXIT_FAILURE; + + memset(&state, 0x00, sizeof(struct SweepState)); + state.serialFd = fd; + state.settleTime = 100000; + state.start = atof(argv[2]); + state.stop = atof(argv[3]); + state.freq = state.start * 1000000.0; + serialPutS(fd, "\r\r\rtuner.init!\r\r\r"); + setFreq(fd, state.freq); + + if((stream = audioOpen(&state)) == NULL) { + serialClose(fd); + return EXIT_FAILURE; + } + + fprintf(stderr, "sweep running...\n"); + + while(1) { + sleep(1); + if(state.freq > state.stop * 1000000.0) + break; + fprintf(stderr, "...%.1f MHz...\n", state.freq / 1000000.0); + } + + audioClose(stream); + serialClose(fd); + + return EXIT_FAILURE; +} diff --git a/utils/osmosdr-sweep/src/serial.c b/utils/osmosdr-sweep/src/serial.c new file mode 100644 index 0000000..d237a74 --- /dev/null +++ b/utils/osmosdr-sweep/src/serial.c @@ -0,0 +1,338 @@ +#ifdef WINDOWS
+
+#include <stdio.h>
+#include "serial.h"
+
+static void printErr(const char* text, int err)
+{
+ char errorBuf[256];
+ char* buf = errorBuf;
+ size_t max = sizeof(errorBuf) - 1;
+ memset(errorBuf, 0x00, sizeof(errorBuf));
+
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), LANG_NEUTRAL, buf, (DWORD)max, NULL);
+ fprintf(stderr, "%s: %s\n", text, errorBuf);
+ fflush(stderr);
+}
+
+HANDLE serialOpen(const char* dev)
+{
+ char str[64];
+ HANDLE fd;
+ int err;
+
+ snprintf(str, sizeof(str), "\\\\.\\%s", dev);
+ fd = CreateFileA(str, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if(fd == INVALID_HANDLE_VALUE) {
+ err = GetLastError();
+ if(err == ERROR_FILE_NOT_FOUND) {
+ fprintf(stderr, "could not open %s: serial port does not exist\n", dev);
+ } else {
+ snprintf(str, sizeof(str), "could not open %s", dev);
+ printErr(str, err);
+ }
+ goto failed;
+ }
+
+ if(serialSetBaudrate(fd, 115200) < 0)
+ goto failed;
+
+ return fd;
+
+failed:
+ if(fd != INVALID_HANDLE_VALUE)
+ CloseHandle(fd);
+ return INVALID_HANDLE_VALUE;
+}
+
+void serialClose(HANDLE fd)
+{
+ if(fd != INVALID_HANDLE_VALUE)
+ CloseHandle(fd);
+}
+
+int serialSetBaudrate(HANDLE fd, int baudrate)
+{
+ DCB dcb;
+
+ memset(&dcb, 0x00, sizeof(dcb));
+ dcb.DCBlength = sizeof(dcb);
+ if(!GetCommState(fd, &dcb)) {
+ printErr("error getting serial port state", GetLastError());
+ return -1;
+ }
+ dcb.BaudRate = baudrate;
+ dcb.ByteSize = 8;
+ dcb.StopBits = ONESTOPBIT;
+ dcb.Parity = NOPARITY;
+ dcb.fBinary = 1;
+ dcb.fOutxCtsFlow = 0;
+ dcb.fOutxDsrFlow = 0;
+ dcb.fOutX = 0;
+ dcb.fInX = 0;
+ dcb.fNull = 0;
+ dcb.fTXContinueOnXoff = 0;
+ dcb.fDtrControl = DTR_CONTROL_DISABLE;
+ dcb.fDsrSensitivity = 0;
+ dcb.fRtsControl = RTS_CONTROL_DISABLE;
+ if(!SetCommState(fd, &dcb)) {
+ printErr("error setting serial port state", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
+
+int serialPutC(HANDLE fd, uint8_t c)
+{
+ DWORD bytesWritten;
+
+ if(!WriteFile(fd, &c, 1, &bytesWritten, NULL)) {
+ printErr("error writing to serial port", GetLastError());
+ return -1;
+ }
+
+ return 1;
+}
+
+int serialGetC(HANDLE fd, uint8_t* c, int timeout)
+{
+ COMMTIMEOUTS timeouts;
+ unsigned long res;
+ COMSTAT comStat;
+ DWORD errors;
+
+ if(!ClearCommError(fd, &errors, &comStat)) {
+ printErr("could not reset comm errors", GetLastError());
+ return -1;
+ }
+
+ if(!GetCommTimeouts(fd, &timeouts)) {
+ printErr("error getting comm timeouts", GetLastError());
+ return -1;
+ }
+ timeouts.ReadIntervalTimeout = timeout;
+ timeouts.ReadTotalTimeoutConstant = timeout;
+ timeouts.ReadTotalTimeoutMultiplier = 10;
+ if(!SetCommTimeouts(fd, &timeouts)) {
+ printErr("error setting comm timeouts", GetLastError());
+ return -1;
+ }
+
+ if(!ReadFile(fd, c, 1, &res, NULL)) {
+ printErr("error reading from serial port", GetLastError());
+ return -1;
+ }
+
+ if(res != 1)
+ return -1;
+
+ return *c;
+}
+
+#else // #ifdef WINDOWS
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <poll.h>
+#include "serial.h"
+
+HANDLE serialOpen(const char* dev)
+{
+ HANDLE fd;
+
+ if((fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) {
+ fprintf(stderr, "could not open %s: %s\n", dev, strerror(errno));
+ goto failed;
+ }
+ if(tcflush(fd, TCIOFLUSH) < 0) {
+ fprintf(stderr, "tcflush() failed: %s\n", strerror(errno));
+ goto failed;
+ }
+ if(serialSetBaudrate(fd, 115200) < 0)
+ goto failed;
+
+ return fd;
+
+failed:
+ if(fd >= 0)
+ close(fd);
+ return INVALID_HANDLE_VALUE;
+}
+
+void serialClose(HANDLE fd)
+{
+ if(fd != INVALID_HANDLE_VALUE)
+ close(fd);
+}
+
+int serialSetBaudrate(HANDLE fd, int baudrate)
+{
+ struct termios tios;
+
+ bzero(&tios, sizeof(tios));
+ switch(baudrate) {
+ case 230400:
+ tios.c_cflag = B230400;
+ break;
+
+ case 460800:
+ tios.c_cflag = B460800;
+ break;
+
+ case 921600:
+ tios.c_cflag = B921600;
+ break;
+
+ default:
+ tios.c_cflag = B115200;
+ break;
+ }
+ tios.c_cflag |= CS8 | CLOCAL | CREAD;
+ tios.c_iflag = IGNPAR;
+ tios.c_oflag = 0;
+ tios.c_lflag = ICANON;
+ tios.c_cc[VMIN] = 1;
+ cfmakeraw(&tios);
+
+ if(tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
+ fprintf(stderr, "tcsetattr() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ if(tcflush(fd, TCIOFLUSH) < 0) {
+ fprintf(stderr, "tcflush() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int serialPutC(HANDLE fd, uint8_t c)
+{
+ if(write(fd, &c, sizeof(uint8_t)) != sizeof(uint8_t)) {
+ if(errno == EAGAIN) {
+ struct pollfd pollfd;
+ int res;
+ pollfd.fd = fd;
+ pollfd.events = POLLOUT;
+ pollfd.revents = 0;
+ do {
+ res = poll(&pollfd, 1, 250);
+ } while((res < 0) && (errno == EINTR));
+ if(res > 0) {
+ if(write(fd, &c, sizeof(char)) != sizeof(char)) {
+ fprintf(stderr, "write failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ return 0;
+ }
+ } else if(res == 0) {
+ fprintf(stderr, "write failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ fprintf(stderr, "poll failed: %s\n", strerror(errno));
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "write failed: %s\n", strerror(errno));
+ return -1;
+ }
+ } else {
+ return 0;
+ }
+}
+
+int serialGetC(HANDLE fd, uint8_t* c, int timeout)
+{
+ struct pollfd pollfd;
+ int res;
+
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+
+ do {
+ res = poll(&pollfd, 1, timeout);
+ } while((res < 0) && (errno == EINTR));
+
+ if(res > 0) {
+ if(read(fd, c, sizeof(char)) != sizeof(char)) {
+ fprintf(stderr, "read failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ return 0;
+ }
+ } else if(res == 0) {
+ // timeout
+ return -1;
+ }
+
+ fprintf(stderr, "poll failed: %s\n", strerror(errno));
+ return -1;
+}
+
+#endif // #ifdef WINDOWS
+
+int serialPutS(HANDLE fd, const char* str)
+{
+ while(*str != '\0') {
+ if(serialPutC(fd, *((uint8_t*)str)) < 0)
+ return -1;
+ str++;
+ }
+ return 0;
+}
+
+int serialGetS(HANDLE fd, char* str, int len, int timeout)
+{
+ int todo = len;
+ uint64_t endTime = getTickCount() + timeout;
+
+ while(todo > 0) {
+ uint64_t now = getTickCount();
+
+ if(now < endTime) {
+ uint8_t c;
+ if(serialGetC(fd, &c, endTime - now) < 0)
+ return -1;
+ *((uint8_t*)str) = c;
+ str++;
+ todo--;
+ } else {
+ break;
+ }
+ }
+
+ return (todo > 0) ? -1 : 0;
+}
+
+int serialExpect(HANDLE fd, const char* str, int timeout)
+{
+ int todo = strlen(str);
+ uint64_t endTime = getTickCount() + timeout;
+
+ while(todo > 0) {
+ uint64_t now = getTickCount();
+
+ if(now < endTime) {
+ uint8_t c;
+ if(serialGetC(fd, &c, endTime - now) < 0)
+ return -1;
+ //fprintf(stderr, "{%c}", c);
+ if(c == (uint8_t)(*str++)) {
+ todo--;
+ continue;
+ } else {
+ return -1;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return (todo > 0) ? -1 : 0;
+}
diff --git a/utils/osmosdr-sweep/src/serial.h b/utils/osmosdr-sweep/src/serial.h new file mode 100644 index 0000000..c31e281 --- /dev/null +++ b/utils/osmosdr-sweep/src/serial.h @@ -0,0 +1,16 @@ +#ifndef INCLUDE_SERIAL_H
+#define INCLUDE_SERIAL_H
+
+#include "utils.h"
+
+HANDLE serialOpen(const char* dev);
+void serialClose(HANDLE fd);
+int serialSetBaudrate(HANDLE fd, int baudrate);
+int serialPutC(HANDLE fd, uint8_t c);
+int serialGetC(HANDLE fd, uint8_t* c, int timeout);
+
+int serialPutS(HANDLE fd, const char* str);
+int serialGetS(HANDLE fd, char* str, int len, int timeout);
+int serialExpect(HANDLE fd, const char* str, int timeout);
+
+#endif // INCLUDE_SERIAL_H
diff --git a/utils/osmosdr-sweep/src/utils.c b/utils/osmosdr-sweep/src/utils.c new file mode 100644 index 0000000..1888b73 --- /dev/null +++ b/utils/osmosdr-sweep/src/utils.c @@ -0,0 +1,11 @@ +#include <time.h> +#include "utils.h" + +uint64_t getTickCount() +{ + struct timespec t; + + if(clock_gettime(CLOCK_MONOTONIC, &t) != 0) + return 0; + return (((uint64_t)t.tv_sec) * 1000) + (((uint64_t)t.tv_nsec) / 1000000); +} diff --git a/utils/osmosdr-sweep/src/utils.h b/utils/osmosdr-sweep/src/utils.h new file mode 100644 index 0000000..0084832 --- /dev/null +++ b/utils/osmosdr-sweep/src/utils.h @@ -0,0 +1,11 @@ +#ifndef INCLUDE_UTILS_H +#define INCLUDE_UTILS_H + +#include <stdint.h> + +uint64_t getTickCount(); + +typedef int HANDLE; +#define INVALID_HANDLE_VALUE (-1) + +#endif // INCLUDE_UTILS_H |