K8090 with Linux


#1

Hello,

I need to operate the K8090 under Linux.

I have searched google and this forum for clues. No Luck.

Is there example code available to do this? Has anyone made this board work with Linux?

Thanks,
Dan


#2

Hasn’t been done yet, though it should be possible


#3

I am currently trying to do the same thing. There is a great PDF document named “K8090/VM8090 Protocol Manual” that describes the USB protocol, it comes with the K8090 demo program for Windows. I tried implementing that protocol using the Linux USB API (using usb_open(), usb_interrupt_write(), …), but so far I can’t provoke any reaction from the K8090. So I spied on the Windows demo application using USBTrace to see how it does things. For one thing, it does not calculate the checksums as outlined in the documentation (it’s off by one). But I can’t get the K8090 to work even with the same “wrong” checksums. So there must be another trick to it. I’ll try a few more things and let you know if I am successful. In the meantime, please let me know if you were more lucky.


#4

Never mind, I found a way: Since the K8090 is a CDC device, the kernel will automatically assign a /dev/ttyACM* device to it, so you don’t really need to use the USB API - you can communicate with the device using normal file system operations. I managed to control it under Linux this way:

  • connect the K8090 to the computer, then run “dmesg” and look at the last message. it will tell you which ttyACM device the kernel assigned to the K8090 (in my case it was ttyACM0).
  • continuously read from that device, because otherwise the board will lock up. you can do that by running “while true; do cat /dev/ttyACM0; done” in a terminal window, for starters
  • write commands to the ttyACM-device according to Velleman’s “K8090 Protocol Manual”, but add 1 to the checksum (the documented checksum algorithm is wrong)
  • Example: The following command will toggle relay 1 (type this command in another terminal window):
    printf ‘\x04\x14\x01\x00\x00\xe7\x0f’ >>/dev/ttyACM0

#5

That is correct, it is a CDC device. Under Windows this translates to using the usbser.sys driver to create a virtual serial port.

The checksum formula should be correct, I’ll take a look to see what’s wrong…

Anyway, the C code used by the DLL to calculate the checksum:

// BYTE = unsigned char
// LPBYTE = unsigned char *
// DWORD = unsigned int (32-bit)

BYTE WINAPI CalculateChecksum(LPBYTE lpData, DWORD dwSize)
{
	BYTE bResult = 0x00;
	DWORD i = 0;

	for (; i < dwSize; ++i)
		bResult += lpData[i];

	return (~bResult + 1);
}

// lpData contains the packet in raw format
// dwSize is the number of bytes, not including the checksum byte (= 5)

#6

I have followed your example and it works perfectly on my linux box for relay 1 using the following:

printf ‘\x04\x14\x01\x00\x00\xe7\x0f’ >>/dev/ttyACM0

Could you give me some pointers for the other 7? Or a little tutorial as to what the above means! it looks like hex code to me and I assume this is translated to a binary string but I am at a bit of a loss as to get the right combination. The manual you refer to specifies the last significant byte indicates which relay is specified.

Any help would be appreciated.

Thanks,
Pete.


#7

ok so I worked it out… a little bin to hex to dec conversion…

from the previous post example for relay 1.
\x04\x14\x01\x00\x00\xe7\x0f

the second ‘x14’ is the command… in this case toggle relay.
the third is the relay based on bit 0-7 (relay 1-8) position in hex. 01, 02, 04, 08, 10, 20, 40, 80
and the second from last is the check sum (see docs quoted above). e7, e6, e4, e0, d8, c8, a8, 68

Putting it all together to toggle each of the relays individually:

relay1
printf ‘\x04\x14\x01\x00\x00\xe7\x0f’ >>/dev/ttyACM0
relay2
printf ‘\x04\x14\x02\x00\x00\xe6\x0f’ >>/dev/ttyACM0
relay3
printf ‘\x04\x14\x04\x00\x00\xe4\x0f’ >>/dev/ttyACM0
relay4
printf ‘\x04\x14\x08\x00\x00\xe0\x0f’ >>/dev/ttyACM0
relay5
printf ‘\x04\x14\x10\x00\x00\xd8\x0f’ >>/dev/ttyACM0
relay6
printf ‘\x04\x14\x20\x00\x00\xc8\x0f’ >>/dev/ttyACM0
relay7
printf ‘\x04\x14\x40\x00\x00\xa8\x0f’ >>/dev/ttyACM0
relay8
printf ‘\x04\x14\x80\x00\x00\x68\x0f’ >>/dev/ttyACM0

the third term can be combined to toggle all or any selection of relays simultaneously. I think I may put a simple c library together to make this easier to incorporate into my own projects. As soon as I have something working I will post it on github so other can use/modify/improve.

Pete.


#8

A first pass…

Anyone have any comments or suggestions…

/*
k8090cmd.cpp
Copyright 2011 Peter Nevill

This file is part of K8090CMD linux command line tool.

k8090cmd 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.

k8090cmd 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 k8090.cmd. 
If not, see http://www.gnu.org/licenses/.


This is a proof of concept cmdline tool to interface with the Velleman K8090 8 channel
usb board. So far I have the on, off an toggle working. I think I have the timer and 
mode setting ok. I am having trouble reading back the response packets from the board.

to compile gcc ./k8090cmd.c -o k8090cmd

to run:

./k8090cmd <command> <bin>

Where command is:

	"t" for toggle
	"o" for on
	"f" for off
	--try the others (see below)

	and bin is the dec number in bytes of the packet:

	relay 1 = 1
	relay 2 = 2
	relay 3 = 4
	relay 4 = 8
	relay 5 = 16
	relay 6 = 32
	relay 7 = 64
	realy 8 = 128

so to toggle relay 4:

./k8090cmd t 8

to toggle relay 3 and 6 (4+32)

./k8090cmd t 36

*/

#include "stdio.h"
#include "k8090.h"
#include <fcntl.h>

//convert input to int.
int Convert_StringToInt(char *text, int *i)
{
        return sscanf(text, "%d", i);
}

//flip all bits
int flipbit(unsigned a)
{
	if (a > 255 ) a ^= (1UL << 8);
	a ^= (1UL << 0);
	a ^= (1UL << 1);
        a ^= (1UL << 2);
        a ^= (1UL << 3);
        a ^= (1UL << 4);
        a ^= (1UL << 5);
        a ^= (1UL << 6);
        a ^= (1UL << 7);

	return a;
}

void main(int argc,char *argv[])
{
	char *sComPort = "/dev/ttyACM0";
        char reply[7];
	int i, cksum, MASK, CMD, param1, param2;
	int iOut, fd; // file descriptor

	MASK=0;
	param1=0;
	param2=0;

	//set which command to apply
	//toggle relays
	if ( strcmp( argv[1], "t") == 0 ) {
		CMD = RTOG;
		Convert_StringToInt(argv[2], &MASK);
	}
	//turn on relays
	if ( strcmp( argv[1], "o") == 0 ) {
		CMD = RON;
		Convert_StringToInt(argv[2], &MASK);
	}
	//turn off relays
	if ( strcmp( argv[1], "f") == 0 ) {
		CMD = ROFF;
		Convert_StringToInt(argv[2], &MASK);
	}
	//set relay mode
	if ( strcmp( argv[1], "m") == 0 ) {
		CMD = RMOD;
		if ( strcmp( argv[3], "0") == 0 ) {
			Convert_StringToInt(argv[2], &MASK);
			printf("%d %d\n",CMD,MASK);
		}
		if ( strcmp( argv[3], "1") == 0 ) {
			MASK=0;
                        Convert_StringToInt(argv[2], &param1);
                }
		if ( strcmp( argv[3], "2") == 0 ) {
                        MASK=0;
			param1=0;
                        Convert_StringToInt(argv[2], &param2);
                }
	}
	//set delay timer
	if ( strcmp( argv[1], "d") == 0 ) {
                CMD = DELAY;
		Convert_StringToInt(argv[2], &MASK);

		//get time from input
		Convert_StringToInt(argv[3], &param2);

		if (param2 > 255 ) { 
			param1=param2-255;
			param2=255;
		}else {
			param1 =0;
		}

        }
	//set start timer
	if ( strcmp( argv[1], "r") == 0 ) {
                CMD = STIME;
                Convert_StringToInt(argv[2], &MASK);

		//get time from input   
                Convert_StringToInt(argv[3], &param2);

                if (param2 > 255 ) {
                        param1=param1-255;
                	param2=255;
                }else {
                        param1 =0;
                }

        } 
	if ( strcmp( argv[1], "q") == 0 ) {
		CMD = QSTAT;
		printf("Query Relay Status\n");
	}
	 
	if ( strcmp( argv[1], "b") == 0 ) {
                CMD = QBMOD;
        }

	if ( strcmp( argv[1], "y") == 0 ) {
                CMD = QTIME;

                Convert_StringToInt(argv[2], &MASK);

                if ( strcmp( argv[3], "1") == 0 ) {
                        param1=1;
                }

        }


	cksum = flipbit(STX + CMD + MASK + param1 + param2) + 1;

	//open link to board.
	fd = open(sComPort, O_RDWR | O_NOCTTY | O_NONBLOCK);

	//If successful
	if(fd>0){


		unsigned char data[7];//= { 0x04, 0x14, 0x01, 0x00, 0x00, 0xe7, 0x0f};
		data[0] = (unsigned char)STX;
		data[1] = (unsigned char)CMD;
		data[2] = (unsigned char)MASK; //the relays to update (binary)
		data[3] = (unsigned char)param1;
		data[4] = (unsigned char)param2;
		data[5] = (unsigned char)cksum; //the computed checksum
		data[6] = (unsigned char)ETX;
		write (fd,data,7); //write to board.


		printf("Open successfully\n");

		close(fd);
	} else {
		printf("failed to open device\n");
	}

}

and the header file

/* k8090.h
 *Copyright 2011 Peter Nevill

This file is part of K8090CMD linux command line tool.

k8090cmd 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.

k8090cmd 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 k8090.cmd.
If not, see http://www.gnu.org/licenses/.


This is a proof of concept cmdline tool to interface with the Velleman K8090 8 channel
usb board. So far I have the on, off an toggle working. I think I have the timer and
mode setting ok. I am having trouble reading back the response packets from the board.
 *
 */
#ifndef K8090_H__
#define K8090_H__

//Packet delimiters
#define STX   0x04 	//
#define ETX   0x0f 	//

//Commands
#define RON   0x11 	// switch on
#define ROFF  0x12 	// switch off
#define RTOG  0x14 	// toggle
#define RMOD  0x21 	// button mode
			//momentary, toggle or timed.
#define STIME 0x41 	// start timer
#define DELAY 0x42 	// set delay

//Query board
#define QSTAT 0x18	// query relay status
#define QTIME 0x44	// query timer delay
#define QBMOD 0x22	// query button mode

//Event commands


#endif

#9

Always try to keep your code modular so it can be reused/extended easily. Linux/C already have a lot of libraries and functions available for you, like atoi (string to int) and getopt (command line parsing), try to incorporate them into your application.

Inverting a byte can be done more easily:

unsigned char a = 0x56;
unsigned char inverted_a = ~a;

The following code for example could be broken up into several .h and .c files that can be reused:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Packet delimiters
#define STX   0x04    // start of packet
#define ETX   0x0f    // end of packet

// Command
#define K8090_SWITCH_RELAY_ON   0x11    // switch on
#define K8090_SWITCH_RELAY_OFF  0x12    // switch off
#define K8090_TOGGLE_RELAY      0x14    // toggle
#define K8090_SET_BUTTON_MODE   0x21    // button mode
#define K8090_START_TIMER       0x41    // start timer
#define K8090_SET_DELAY         0x42    // set delay

typedef struct
{
	unsigned char stx;
	unsigned char cmd;
	unsigned char mask;
	unsigned char param1;
	unsigned char param2;
	unsigned char chk;
	unsigned char etx;
}
K8090_PACKET, *PK8090_PACKET;

int k8090_connect(const char*port)
{
	return open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
}

unsigned char k8090_finalize(PK8090_PACKET packet)
{
	packet->stx = STX;
	packet->chk = ~(packet->stx + packet->cmd + packet->mask + packet->param1 + packet->param2) + 1;
	packet->etx = ETX;
}

int k8090_send(int fd, unsigned char cmd, unsigned char mask, unsigned char param1, unsigned char param2)
{
	K8090_PACKET packet;

	packet.cmd    = cmd;
	packet.mask   = mask;
	packet.param1 = param1;
	packet.param2 = param2;

	k8090_finalize(&packet);

	return write (fd, &packet, sizeof(packet));
}

void k8090_close(int fd)
{
	close(fd);
}

void main(int argc,char *argv[])
{
	char *tty = "/dev/ttyACM0";

	int k8090 = k8090_connect(tty);
	
	if (k8090)
	{
		// This whole code should be replaced with getopt/getopt_long
		// which is more robust and has a lot more features

		if (strlen(argv[1]) == 1) {

			switch(argv[1][0])
			{
			case 't':
				k8090_send(k8090, K8090_TOGGLE_RELAY, atoi(argv[2]), 0x00, 0x00);
				break;
			default:
				// unknown command
				break;
			}
		}

		k8090_close(k8090);
	}
}

#10

Thanks VEL448. As you can see I am more of a hacker than a programmer. Thanks for taking the time to make my concept “beautiful”, great job. Any pointers on reading back when the status cmd is sent?

Pete.


#11

For what you are trying to do, simply performing a read after sending a command might work since each command has one or more response packets.

The more code/ideas people share, the more other people will benefit from it :slight_smile:


#12

I am really struggling to get status from the K8090 board.

I do the following:

#define K8090_FIRMWARE_VERSION  0x71    // firmware version

k8090_send(k8090, K8090_FIRMWARE_VERSION, 0x00, 0x00, 0x00);

void k8090_read(int fd) {

        sleep(1);
        K8090_PACKET_READ packet;
        read(fd, &packet, 7);
        printf("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
packet.stx,packet.cmd,packet.mask,packet.param1,packet.param2,packet.chk,packet.etx);

}

I get junk back… 0xbf 0x00 0x00 0x00 0x00 0x38 0x00

if i run the send twice in succession I get back. 0x71 0x00 0x10 0x06 0x06 0x0f 0xbf
Which is what I expect although the STX is missing there are only 6 bits.

any guidance on reading from the buffer would be good.

Another observation is if I insert

fcntl(fd, F_SETFL, 0);

The out put pauses until I press a button on the board. The board outputs as expected if I run the following in another terminal.

#!/bin/bash
while true;
do 

cat /dev/ttyACM0 | xxd -c 1 -p 


done

Any guidance on reading from the board correctly would be helpful. As you can see I a bit of a noob at this.

Thanks,

Pete.


#13

i got it work to get the status from the board.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

// Packet delimiters
#define STX   0x04    // start of packet
#define ETX   0x0f    // end of packet
#define K8090_FIRMWARE_VERSION  0x18   // firmware version


// Command
#define K8090_SWITCH_RELAY_ON   0x11    // switch on
#define K8090_SWITCH_RELAY_OFF  0x12    // switch off
#define K8090_TOGGLE_RELAY      0x14    // toggle
#define K8090_SET_BUTTON_MODE   0x21    // button mode
#define K8090_START_TIMER       0x41    // start timer
#define K8090_SET_DELAY         0x42    // set delay

typedef struct
{
   unsigned char stx;
   unsigned char cmd;
   unsigned char mask;
   unsigned char param1;
   unsigned char param2;
   unsigned char chk;
   unsigned char etx;
}
K8090_PACKET, *PK8090_PACKET;

int k8090_connect(const char*port)
{
   return open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
}

unsigned char k8090_finalize(PK8090_PACKET packet)
{
   packet->stx = STX;
   packet->chk = ~(packet->stx + packet->cmd + packet->mask + packet->param1 + packet->param2) + 1;
   packet->etx = ETX;
}

int k8090_send(int fd, unsigned char cmd, unsigned char mask, unsigned char param1, unsigned char param2)
{
   K8090_PACKET packet;

   packet.cmd    = cmd;
   packet.mask   = mask;
   packet.param1 = param1;
   packet.param2 = param2;

   k8090_finalize(&packet);

   return write (fd, &packet, sizeof(packet));
}
void k8090_read(int fd) {

       		 sleep(1);
        	K8090_PACKET packet;
        	read(fd, &packet, 7);
        	printf("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
	packet.stx,packet.cmd,packet.mask,packet.param1,packet.param2,packet.chk,packet.etx);

	}
void k8090_close(int fd)
{
   close(fd);
}

void main(int argc,char *argv[])
{
   char *tty = "/dev/ttyACM0";

   int k8090 = k8090_connect(tty);
   
   if (k8090)
   {
      // This whole code should be replaced with getopt/getopt_long
      // which is more robust and has a lot more features

      if (strlen(argv[1]) == 1) {

         switch(argv[1][0])
         {
         case 't':
            k8090_send(k8090, K8090_TOGGLE_RELAY, atoi(argv[2]), 0x00, 0x00);
            break;
	case 'q':
		k8090_send(k8090, K8090_FIRMWARE_VERSION, 0x00, 0x00, 0x00);
		k8090_read(k8090);
break;

         default:
            // unknown command
            break;
         }
      }

      k8090_close(k8090);
   }
}

command : ./k8090cmd q
wil get the status.

hope it help you


#14

Here, missing ‘o’ and ‘f’ bck to code…

status still give random replay…

acc:~# ./k8090cmd q
0x52 0x76 0xb7 0x70 0xe3 0xff 0xbf
acc:~# ./k8090cmd q
0x22 0x7c 0xb7 0x00 0xe8 0xff 0xbf
acc:~# ./k8090cmd q
0xb2 0x7a 0xb7 0xc0 0xfa 0xff 0xbf
acc:~# ./k8090cmd q
0xc2 0x7b 0xb7 0xe0 0xf0 0xff 0xbf

no idea, why…

Card type what i use is: Velleman VM8090 ( P8090-2)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>


// Packet delimiters
#define STX   0x04    // start of packet
#define ETX   0x0f    // end of packet
#define K8090_FIRMWARE_VERSION  0x18   // firmware version


// Command
#define K8090_SWITCH_RELAY_ON   0x11    // switch on
#define K8090_SWITCH_RELAY_OFF  0x12    // switch off
#define K8090_TOGGLE_RELAY      0x14    // toggle
#define K8090_SET_BUTTON_MODE   0x21    // button mode
#define K8090_START_TIMER       0x41    // start timer
#define K8090_SET_DELAY         0x42    // set delay

typedef struct
{
   unsigned char stx;
   unsigned char cmd;
   unsigned char mask;
   unsigned char param1;
   unsigned char param2;
   unsigned char chk;
   unsigned char etx;
}
K8090_PACKET, *PK8090_PACKET;

int k8090_connect(const char*port)
{
   return open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
}

unsigned char k8090_finalize(PK8090_PACKET packet)
{
   packet->stx = STX;
   packet->chk = ~(packet->stx + packet->cmd + packet->mask + packet->param1 + packet->param2) + 1;
   packet->etx = ETX;
}

int k8090_send(int fd, unsigned char cmd, unsigned char mask, unsigned char param1, unsigned char param2)
{
   K8090_PACKET packet;

   packet.cmd    = cmd;
   packet.mask   = mask;
   packet.param1 = param1;
   packet.param2 = param2;

   k8090_finalize(&packet);

   return write (fd, &packet, sizeof(packet));
}
void k8090_read(int fd) {

              sleep(1);
           K8090_PACKET packet;
           read(fd, &packet, 7);
           printf("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
   packet.stx,packet.cmd,packet.mask,packet.param1,packet.param2,packet.chk,packet.etx);

   }
void k8090_close(int fd)
{
   close(fd);
}

void main(int argc,char *argv[])
{
   char *tty = "/dev/ttyACM0";

   int k8090 = k8090_connect(tty);
   
   if (k8090)
   {
      // This whole code should be replaced with getopt/getopt_long
      // which is more robust and has a lot more features

      if (strlen(argv[1]) == 1) {

         switch(argv[1][0])
         {
        case 't':
        	k8090_send(k8090, K8090_TOGGLE_RELAY, atoi(argv[2]), 0x00, 0x00);
	break;

   	case 'q':
      		k8090_send(k8090, K8090_FIRMWARE_VERSION, 0x00, 0x00, 0x00);
      		k8090_read(k8090);
	break;

        case 'o':
                k8090_send(k8090, K8090_SWITCH_RELAY_ON, atoi(argv[2]), 0x00, 0x00);
		printf("Open successfully\n");
        break;

        case 'f':
                k8090_send(k8090, K8090_SWITCH_RELAY_OFF, atoi(argv[2]), 0x00, 0x00);
		printf("Close successfully\n");
        break;

	default:
        	// unknown command
        break;
         }
      }

      k8090_close(k8090);
   }
}

#15

In case this helps, the Xenomai project has contributed a simple command line tool
for controlling the K8090/VM8090 from a linux box.

download.gna.org/xenomai/misc/k8090/

HTH,


Philippe


#16

I’ve read this topic and managed to toggle the relays via a command op the linux commandline. Is it possible to use a linux command to read the status? I don’t want to use c code, only bash.


#17

HI
I am new in linux commands, and will appreciate input.
I bought a VM8090 card and is trying to run it on a linux debain machine.
I download the power.c from gna.org. - download.gna.org/xenomai/misc/k8090/
I could do the following with success:
./power -off=1,2
./power -on 1,2
./power -toggle=1,2
./power -cycle=1,2

but I cant get the -status command to work.
Can someone please help me with the syntax for the -status argument?
Or how can I get the status of the cart from the /ttyACM0 device?


#18

I am trying to open and close the relais on the VM8090 board from a webpage, but i can’t get it working.
I’ve set up an easy cgi-file i googled:

#!/bin/bash
echo "Content-type: text/html"
echo ""
echo "<html><head><title>Bash as CGI"
echo "</title></head><body>"
echo "<h1>Hello world</h1>"
echo "Today is $(date)"
echo "</body></html>"

Where i added

echo "Toggle relais 1 $(./power -on 1)"

It is not a button or something, it just performs the command on load.
But it doesn’t work…

I am very new to this so any help or advice is welcome!
Tried every submitted tool on this thread like the Xenomai’s tool, Peter Nevill’s and also radheads easy command


#19

output=`./power -on 1` echo "Toggle relais 1 $output"


#20

[quote=“VEL448”]output=`./power -on 1` echo "Toggle relais 1 $output"[/quote]

Thanks for the reply, but badly the command i’ve put in all worked.
The only problem is that I keep getting ‘Permission denied’ on opening my /dev/ttyACM0

When I toggle a relay from my terminal it works fine, but when i want to use CGI or PHP, it gives me denied permission.
I’ve tried like everything I could find on google like changing permissions on ttyACM0, adding my usern and apache to dialout etc…

Anyone else tried to do this?