the original script works fine now

so grateful for your patience with this - if you have time and don't mind, adding an OSC dispatcher would be awesome

this will do:

#include <fcntl.h>
#include <linux/input.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <oscpkt.hh>
#include <libraries/UdpClient/UdpClient.h>

std::string gOscAddress = "/foot"; // OSC address. Message format: <address> <encoderId> <encoderValue>
std::string gDestinationIp = "192.168.7.1"; // 192.168.7.1 is the host computer (if it's a Mac or Linux, it would be 192.168.6.1 if it's Windows).
int gDestinationPort = 5555;
UdpClient sock;

static void sendOsc(const std::string& subPath, int value)
{
	oscpkt::PacketWriter pw;
	pw.init();
	std::string address = gOscAddress + subPath;
	oscpkt::Message msg(address);
	pw.addMessage(msg.pushInt32(value));
	printf("%s %d\n", address.c_str(), value);
	if(pw.isOk())
		sock.send((void*)pw.packetData(), pw.packetSize());
}

int main(){
	if(!sock.setup(gDestinationPort, gDestinationIp.c_str()))
	{
		fprintf(stderr, "Unable to send to %s:%d\n", gDestinationIp.c_str(), gDestinationPort);
		return 1;
	}

	// Variables keyboard control
	struct input_event ev;
	const char *dev = "/dev/input/event1";

	// qwerty keyboard capture
	int fd = open(dev, O_RDONLY);
	if (fd == -1) {
		fprintf(stderr, "Cannot open %s:.\n", dev);
		return -1;
	}
	while (1)
	{
		ssize_t n = read(fd, &ev, sizeof ev);
		if (n < 0) {
			fprintf(stderr, "Error while reading: %d %s\n", errno, strerror(errno));
			return -1;
		} else if (n != sizeof(ev)) {
			fprintf(stderr, "Read unexpected length\n");
			return -1;
		} else {
			if (ev.type == EV_KEY && ev.value == 0 && ev.value <= 2)
			{
				// key released
				sendOsc("/released", ev.code);
			}
			if (ev.type == EV_KEY && ev.value == 1 && ev.value <= 2)
			{
				// key pressed
				sendOsc("", ev.code);
			}
		}
	}
	close(fd); // won't actually get here unless an error occurs
	return 0;
}

This sclang code can be used to test it:

thisProcess.openUDPPort(5555); // the port will stay open until you restart sclang or recompile the class library
o = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/foot');
n = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/foot/released');

notes:

  • pressed keys are sent as /foot 67 or similar. This should be the same behaviour you currently had
  • released keys are sent as /foot/released 67
  • to amend the above behaviours, change the first argument to the two calls to sendOsc().
  • as it is above, the program sends to port 5555 on 192.168.7.1, that is on the host computer (if you are on a mac or Linux machine). This is useful for testing, but if you want to pass it to supercollider running on Bela, you will need to change gDestinationIp to 127.0.0.1 or 192.168.7.2 or bela.local.

that's great, thanks so much : ) last question for now, how would I run a .scd file and a .cpp file alongside each other? I do want to pass the OSC msg to SuperCollider running on Bela

for testing purposes, you can run the built project from the command line in a terminal, e.g. assuming your project is called HID, after you build it in the IDE, you'd do:

/root/Bela/projects/HID/HID

Then keep using the IDE for your .scd file. If this goes well, you should then make systemd service out of your program.

Here are some instructions from a WIP knowledge base article:

You need to create a file /lib/systemd/system/hid.service with this content (making sure the project name matches your project name):

[Unit]
Description=HID to OSC Launcher
After=network-online.target

[Service]
ExecStart=/root/Bela/projects/HID/HID
Type=simple
Restart=always
RestartSec=1
WorkingDirectory=/root/Bela/projects/HID
Environment=HOME=/root
KillMode=process

[Install]
WantedBy=default.target

This can be done from the terminal or by creating a file hid.service in your project (make sure the file includes only the text above and none of the automatically-generated content in the file you create) and then linking it into place by running ln -s /root/Bela/projects/O2O/hid.service /lib/systemd/system/hid.service in the Bela IDE console.

You can then start the programme running in the background with systemctl start hid in the console of the IDE. You can stop the programme with systemctl stop hid. Once the programme is running in the background you should be able to send OSC messages to it like before and see the results on the screen.

While this programme is running in the background, you can visualise the process’s output with journalctl -fu hid (if executing the command from the Bela IDE console, use journalctl -n 20 -u hid | cat instead). Once you verify it is working, you can open a different Bela project and try to send OSC to it from the Bela project instead of the host.

Once you are happy with how this working and would like to embed your project you can enable the hid service to start automatically in the background at boot with systemctl enable hid. Later you can disable it with systemctl disable hid.

fantastic, that works great. thanks so much πŸ™‚) time to make some noise!

2 years later

hello, earlier in this thread and long ago you mentioned a build of SuperCollider for Bela with the HID class enabled - would it still be possible to grab this please?

Hmm I'd need to build it again, as that one is long lost. Is this for the same application? Is the solution suggested above not working?

7 days later

Hi Giulio, sorry for the slow reply. It isn't that the above solution has stopped working or anything - it worked great the last time I used it. The HID class would just be a nice-to-have for SuperCollider on Bela - in my case I've been prototyping some new SC patches with various different HID devices on a laptop using this class and was just hoping to avoid the refactor when I move it over to Bela, which is no biggie. But it strikes me as a natural thing to want on Bela, even though I seem to be the first to ask!

makes sense, however the issue is that HID handling is generally done by X11 and we don't have an X11 server running on the board, because its main purpose (as far as I understand it) is to run a GUI and we don't do that. I guess X11 could be made to run without a GUI but I am not sure what steps are needed and I am not sure if it woudl carry any overhead.

If it can help with the refactoring, the ev.code that is reported by the code above is not the ASCII code of the corresponding code. Part of X11's job is to retrieve that ASCII code, which can be non-trivial to do in an exhaustive way. A simple way of retrieving the easiest codes is shown here , so the above program could be expanded with this if needed.

4 months later

Hi Giulio,

Excuse my lack of C++ but can you give pointers (ahem) for adjusting render.cpp (pasted below) to read inputs from two different USB devices connected to the board? I've plugged two HID devices, outputs of which are just mapped to regular qwerty keystrokes, into Bela via a USB splitter but it only ever registers inputs from one of the devices. Any help would be greatly appreciated as always.

Best wishes,

Mark

#include <fcntl.h>
#include <linux/input.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <oscpkt.hh>
#include <libraries/UdpClient/UdpClient.h>

std::string gOscAddress = "/foot"; // OSC address. Message format: <address> <encoderId> <encoderValue>
std::string gDestinationIp = "127.0.0.1"; //"bela.local"; // 192.168.7.1 is the host computer (if it's a Mac or Linux, it would be 192.168.6.1 if it's Windows).
int gDestinationPort = 5555;
UdpClient sock;

static void sendOsc(const std::string& subPath, int value)
{
	oscpkt::PacketWriter pw;
	pw.init();
	std::string address = gOscAddress + subPath;
	oscpkt::Message msg(address);
	pw.addMessage(msg.pushInt32(value));
	printf("%s %d\n", address.c_str(), value);
	if(pw.isOk())
		sock.send((void*)pw.packetData(), pw.packetSize());
}

int main(){
	if(!sock.setup(gDestinationPort, gDestinationIp.c_str()))
	{
		fprintf(stderr, "Unable to send to %s:%d\n", gDestinationIp.c_str(), gDestinationPort);
		return 1;
	}

	// Variables keyboard control
	struct input_event ev;
	const char *dev = "/dev/input/event1";

	// qwerty keyboard capture
	int fd = open(dev, O_RDONLY);
	if (fd == -1) {
		fprintf(stderr, "Cannot open %s:.\n", dev);
		return -1;
	}
	while (1)
	{
		ssize_t n = read(fd, &ev, sizeof ev);
		if (n < 0) {
			fprintf(stderr, "Error while reading: %d %s\n", errno, strerror(errno));
			return -1;
		} else if (n != sizeof(ev)) {
			fprintf(stderr, "Read unexpected length\n");
			return -1;
		} else {
			if (ev.type == EV_KEY && ev.value == 0 && ev.value <= 2)
			{
				// key released
				sendOsc("/released", ev.code);
			}
			if (ev.type == EV_KEY && ev.value == 1 && ev.value <= 2)
			{
				// key pressed
				sendOsc("", ev.code);
			}
		}
	}
	close(fd); // won't actually get here unless an error occurs
	return 0;
}

how are you starting this ? The easiest way would be to have two instances of the process reading each from a different port and sending it to a different OSC address.

E.g.:

int main(int argc, char** argv){
	if(argc != 3) {
		fprintf(stderr, "Needs two arguments: input port and OSC address\n");
		return -1
	}
	if(!sock.setup(gDestinationPort, gDestinationIp.c_str()))
	{
		fprintf(stderr, "Unable to send to %s:%d\n", gDestinationIp.c_str(), gDestinationPort);
		return 1;
	}

	// Variables keyboard control
	struct input_event ev;
	const char *dev = argv[1];
	gOscAddress = argv[2];
....

then start one with ./my-program /dev/input/event1 /foot and the other one with ./my-program /dev/input/event2 /hand
.

The next step up would be to run it all in one process, but with two different threads. The next one ("proper" one) would be to use select() or poll() to check which of all the open /dev/inputX devices is ready before reading from it from a single thread. Anyhow try the solution above and if it's not good, we can do the next steps.

Currently I'm starting it from inside the SuperCollider script with "/root/Bela/projects/HID/HID".unixCmd;. I'll try and make the changes you suggest and report back. Thanks as always

In that case the multi-process approach should be fine

2 months later

Hi,
I am trying to use a graphic writing tablet (Wacom intuous), and used the HID class with SuperCollider on Linux; on the Bela it is not working for the reasons discussed above.
Could you please summarize how this can be done, as I have no experience in C++ programming and could not completely follow the above posts?
The bela then has to auto-start both this receive-send patch and my SuperCollider patch, is that possible?
Many Thanks, Katharina

I have a solution: this works for getting data from a Wacom intuous tablet on Bela and send it to OSC:

/** 
 * How to use on Bela:
 * Create a new C++ project on Bela and replace the content of its render.cpp file with this file.
 * Make sure, compiler constant BELA is defined (the #define BELA statement below is uncommented)
 * Replace the WACOM_DEVICE by the actual device (event), the WACOM is assigned to
*/

#define BELA

#define WACOM_DEVICE "/dev/input/event9"

/**
 * How to read the data in SuperCollider:

thisProcess.openUDPPort(5555); // the port will stay open until you restart sclang or recompile the class library
x = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/wacom/x');
y = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/wacom/y');
pressed = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/wacom/pressed');
misc = OSCFunc({ arg msg, time, addr, recvPort; [msg, time, addr, recvPort].postln; }, '/wacom/misc');

*/

#include <fcntl.h>
#include <linux/input.h>
#include <errno.h>
#include <stdio.h>
#include <string>
#include <unistd.h>

#ifdef BELA 
#include <oscpkt.hh>
#include <libraries/UdpClient/UdpClient.h>

std::string gOscAddress = "/wacom"; // OSC address. Message format: <address> <encoderId> <encoderValue>
std::string gDestinationIp = "192.168.7.1"; // address of the host computer
int gDestinationPort = 5555;
UdpClient sock;
#endif // BELA

static void sendOsc(const std::string& subPath, const double value)
{
#ifdef BELA   
	oscpkt::PacketWriter pw;
	pw.init();
	std::string address = gOscAddress + subPath;
	oscpkt::Message msg(address);
	pw.addMessage(msg.pushDouble(value));
	printf("%s %d\n", address.c_str(), value);
	if(pw.isOk()) {
		sock.send((void*)pw.packetData(), pw.packetSize());
    }
#endif // BELA
}

int main() {
#ifdef BELA
	if(!sock.setup(gDestinationPort, gDestinationIp.c_str()))
	{
		fprintf(stderr, "Unable to send to %s:%d\n", gDestinationIp.c_str(), gDestinationPort);
		return 1;
	}
#endif // BELA

	const char *dev = WACOM_DEVICE;

	// qwerty keyboard capture
	int fd = open(dev, O_RDONLY);
	if (fd == -1) {
		fprintf(stderr, "Cannot open %s:.\n", dev);
		return -1;
	}

    struct input_absinfo absinfo;
    int x_min = 0;
    int x_max = 0;
    int y_min = 0;
    int y_max = 0;

    if (ioctl(fd, EVIOCGABS(ABS_X), &absinfo) == -1) {
        fprintf(stderr, "Failed to get X axis abs info\n");
        return -1;
    }

    x_min = absinfo.minimum;
    x_max = absinfo.maximum;
    printf("X value range [%d, %d]", x_min, x_max);

    if (ioctl(fd, EVIOCGABS(ABS_Y), &absinfo) == -1){
        fprintf(stderr, "Failed to get Y axis abs info\n");
        return -1;
    }

    y_min = absinfo.minimum;
    y_max = absinfo.maximum;
    printf("Y value range [%d, %d]", y_min, y_max);

    double x_position_normalized = 0.0;
    double y_position_normalized = 0.0;
    int event_size = 0;
    struct input_event ev;

	while (1) {
        event_size = read(fd, &ev, sizeof(struct input_event));
        if (event_size != sizeof(struct input_event)) {
            printf("wrong size of event: %d\n", event_size);
            return -1;
        }

        switch (ev.type) {
            case EV_ABS:
                switch (ev.code) {
                    case ABS_X:
                        x_position_normalized = (double) (ev.value - x_min) / (x_max - x_min);   
                        #ifndef BELA
                        printf("X: %f\n", x_position_normalized);
                        #endif // BELA
                        sendOsc("/x", x_position_normalized);
                        break;

                    case ABS_Y:
                        y_position_normalized = (double) (ev.value - y_min) / (y_max - y_min); 
                        #ifndef BELA 
                        printf("Y: %f\n", y_position_normalized);
                        sendOsc("/y", y_position_normalized);
                        #endif // BELA
                        break;
                }
                break;

            case EV_KEY:
                printf("Key pressed: value %d, code %d\n",ev.value, ev.code);
                sendOsc("/pressed", 1.0);
                break;

            case EV_MSC:
                printf("Msc event: value: %d, code %d\n",ev.value, ev.code);
                sendOsc("/misc", 1.0);
                break;
        }

        usleep(10000);
    }
	close(fd); // won't actually get here unless an error occurs

	return 0;
}