Dream Cheeky Drivers
- Dream Cheeky Drivers Reviews
- Dream Cheeky Drivers Video
- Dream Cheeky Drivers Online
- Dream Cheeky Drivers Review
- Dream Cheeky Drivers
Nodejs API for controller dream cheeky missile launcher that works on windows.This is a reinforcement of guillaumedelre's dream-cheeky-thunder-driver library, with adjustments to run on windows and improvements on led
and execute commands
features.It should work on linux as well, since the kernel check is not necessary for the device.
The USB Message Board from Dream Cheeky. Ok, so it has a silly name, but it is a cute little scrolling led display none the less. Unfortunately, it is for Windows only. When I found out that the Hardware Developer Manual was available though, I thought it would be fun to try and write a Linux driver for it. Version 1.0 took a long saturday. Elixir Cross Referencer. Check our new online training!
npm install dream-cheeky-windows-driver
- In your js file, add
var DCDriver = require('dream-cheeky-windows-driver');
DCDriver
DCDriver.DEVICE_CONSTANTS
Description:
A JS object containing constants for Dream Cheeky Thunder Launcher.
DCDriver.turnOnDebugMode()
Description:
Turn on debug mode (level 4) of USB.
DCDriver.turnOffDebugMode()
Description:
Turn off USB debug mode (ie. debug level 0).
DCDriver.moveUp(durationMS
[, callback
])
Description:
Move up for a period of time.
Parameters:
durationMS
Type: Number
Duration of moving time in ms.
callback
Type: Function()
A function to execute when the movement is done.
DCDriver.moveDown(durationMS
[, callback
])
Description:
Dream Cheeky Drivers Reviews
Move down for a period of time.
Parameters:
durationMS
Type: Number
Duration of moving time in ms.
callback
Type: Function()
A function to execute when the movement is done.
DCDriver.moveLeft(durationMS
[, callback
])
Description:
Move left for a period of time.
Parameters:
durationMS
Type: Number
Duration of moving time in ms.
callback
Type: Function()
A function to execute when the movement is done.
DCDriver.moveRight(durationMS
[, callback
])
Description:
Move right for a period of time.
Parameters:
durationMS
Type: Number
Duration of moving time in ms.
callback
Type: Function()
A function to execute when the movement is done.
DCDriver.stop([callback
])
Stop movement immediately.
Parameters:
callback
Type: Function()
A function to execute when the movement is stopped.
DCDriver.fire(numberOfShot
[, callback
])
Shoot for numberOfShot
times consecutively.
Dream Cheeky Drivers Video
Parameters:
numberOfShot
Type: Number
Number of shots
callback
Type: Function()
A function to execute when all the shooting is done (and when the target is utterly destroyed!).
DCDriver.park([callback
])
Go back to a fixed default location (leftmost and bottommost position).
Parameters:
callback
Type: Function()
A function to execute when all the movement is done.
DCDriver.execute(commands
[, callback
])
NOT STABLE yet, will be improved.
Convenient method to specify a chain of commands
Parameters:
commands
Type: String
callback
Type: Function()
A function to execute when the chain of commands is done.
DCDriver.led(commands
[, callback
])
Activate the led on th device
Parameters:
commands
Type: String
callback
Type: Function()
A function to execute when done.
I read a blog post by Matthias Vallentin a while back about getting a USB missile launcher working and thought that a similar gadget would be a nice candidate for the 2012 refresh of our successful EL503 Developing for Embedded Linux course so ordered a nice looking piece of hardware from Amazon – the Thunder Missile Launcher from Dream Cheeky.
Sadly these guys don’t provide Linux drivers and the hardware wasn’t an exact match for the launcher used in Matthias’ article so I had to do a bit of research.
Normally it would be a case of firing up my Windows partition and using a USB Sniffer but I didn’t want to shutdown my system as I was in the middle of quite a few projects so I hit the net and stumbled across the Retaliation project which used the Thunder Launcher to punish people who broke a CI build and a blog piece by Nathan Milford which had the requisite USB command codes but had programs that leveraged libUSB from userspace.
So now I knew what I had to feed my launcher and had some good resources on Linux USB[1] [2] [3] now I could start!
Echoing Matthias’ steps – I created a simple core driver that would print info out in the probe
/* Rocket launcher specifics */
#define LAUNCHER_VENDOR_ID 0x2123
#define LAUNCHER_PRODUCT_ID 0x1010
#define LAUNCHER_NODE 'launcher'
static struct usb_class_driver class;
/* Table of devices that work with this driver */
static struct usb_device_id launcher_table[] =
{
{ USB_DEVICE(LAUNCHER_VENDOR_ID, LAUNCHER_PRODUCT_ID) },
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, launcher_table);
static struct usb_driver launcher_driver =
{
.name = 'launcher_driver',
.id_table = launcher_table,
};
static int launcher_open(struct inode *inodep, struct file *filp)
{
int retval = 0
pr_debug('launcher_openn');
/* Save our object in the file's private structure. */
/* filp->private_data = dev; */
return retval;
}
static int launcher_close(struct inode *inodep, struct file *filp)
{
int retval = 0;
pr_debug('launcher_closen');
/* We can get private data from filp->private_data; */
return retval;
}
static ssize_t launcher_read(struct file *f, char __user *buf, size_t cnt,
loff_t *off)
{
int retval = -EFAULT;
pr_debug('launcher_readn');
return retval;
}
static ssize_t launcher_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *off)
{
int retval = -EFAULT;
pr_debug('launcher_writen');
/* We can get private data from filp->private_data; */
return retval;
}
static struct file_operations fops =
{
.open = launcher_open,
.release = launcher_close,
.read = launcher_read,
.write = launcher_write,
};
static int launcher_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct usb_host_interface *iface_desc;
int retval = -ENODEV;
pr_debug('launcher_proben');
/* Set up our class */
class.name = LAUNCHER_NODE'%d';
class.fops = &fops;
if ((retval = usb_register_dev(interface, &class)) minor);
}
/* This will be our private interface device data instead of NULL eventually */
/* usb_set_intfdata(interface, NULL); */
return retval;
}
static void launcher_disconnect(struct usb_interface *interface)
{
pr_debug('launcher_disconnectn');
/* Give back our minor. */
usb_deregister_dev(interface, &class);
}
static int __init launcher_init(void)
{
int result;
pr_debug('launcher_disconnectn');
/* Wire up our probe/disconnect */
launcher_driver.probe = launcher_probe;
launcher_driver.disconnect = launcher_disconnect;
/* Register this driver with the USB subsystem */
if ((result = usb_register(&launcher_driver))) {
pr_err('usb_register() failed. Error number %d', result);
}
return result;
}
static void __exit launcher_exit(void)
{
/* Deregister this driver with the USB subsystem */
usb_deregister(&launcher_driver);
}
module_init(launcher_init);
module_exit(launcher_exit);
I compiled this with the makefile, inserted the module and saw that my init call had successfully been called so I popped the launchers USB into my laptop and…. nothing.
I checked dmesg and saw it had been claimed by hid-generic, part of the usbhid module on my laptop!
[205211.636201] usb 2-1.2: >USB disconnect, device number 23
[205214.371289] usb 2-1.2: >new low-speed USB device number 24 using ehci_hcd
[205214.462969] usb 2-1.2: >New USB device found, idVendor=2123, idProduct=1010
[205214.462979] usb 2-1.2: >New USB device strings: Mfr=1, Product=2, SerialNumber=0
[205214.462985] usb 2-1.2: >Product: USB Missile Launcher
[205214.462990] usb 2-1.2: >Manufacturer: Syntek
[205214.468668] hid-generic 0003:2123:1010.000B: >hiddev0,hidraw0: USB HID v1.10 Device [Syntek USB Missile Launcher] on usb-0000:00:1d.0-1.2/input0
To get hidusb to release it, you can tell it to unbind using sysfs.
[nick@slimtop ~]$ ls /sys/bus/usb/drivers/usbhid/
2-1.2:1.0 bind module new_id remove_id uevent unbind
^^^ Our address
[nick@slimtop ~] sudo sh -c 'echo 2-1.2:1.0 > /sys/bus/usb/drivers/usbhid/unbind'
[nick@slimtop ~]$ ls
bind module new_id remove_id uevent unbind
To get it to bind to our driver we would have to poke it manually
[nick@slimtop ~]$ sudo sh -c 'echo '2-1.2:1.0' > /sys/bus/usb/drivers/launcher_driver/bind'
Checking dmesg again showed that the probe function had been called and we had our /dev/launcher0!
I now needed to update the information expected by the driver. The original launcher used by Matthias’ had different codes for most of the commmands so using the userspace information we could update the command codes and tweak the control codes.
#define LAUNCHER_NODE 'launcher'
#define LAUNCHER_CTRL_BUFFER_SIZE 8
#define LAUNCHER_CTRL_REQUEST_TYPE 0x21
#define LAUNCHER_CTRL_REQUEST 0x09
#define LAUNCHER_CTRL_VALUE 0x0
#define LAUNCHER_CTRL_INDEX 0x0
#define LAUNCHER_CTRL_COMMAND_PREFIX 0x02
Dream Cheeky Drivers Online
#define LAUNCHER_STOP 0x20
#define LAUNCHER_UP 0x02
#define LAUNCHER_DOWN 0x01
#define LAUNCHER_LEFT 0x04
#define LAUNCHER_RIGHT 0x08
#define LAUNCHER_UP_LEFT (LAUNCHER_UP LAUNCHER_LEFT)
#define LAUNCHER_DOWN_LEFT (LAUNCHER_DOWN LAUNCHER_LEFT)
#define LAUNCHER_UP_RIGHT (LAUNCHER_UP LAUNCHER_RIGHT)
#define LAUNCHER_DOWN_RIGHT (LAUNCHER_DOWN LAUNCHER_RIGHT)
#define LAUNCHER_FIRE 0x10
Our USB command code takes the form of:
REQUEST_TYPE REQUEST VALUE INDEX DATA
where DATA is an 8 byte buffer with the first set to 0x8, our COMMAND_PREFIX.
For example, to send a fire command (0x10) we would use a usb_control_msg like this
int usb_control_msg ( struct usb_device *dev,
unsigned int pipe,
0x09, // Request
0x21, // Request Type
0x0, // Value
0x0, // Index
&buffer, // Buffer
sizeof(buffer), // Length of buffer
int timeout);
where buffer would be a blank char buffer of 8 bytes with the first and second bytes set to the COMMAND_PREFIX and command code respectively which you can see in this snipped from our launcher_write() function
...
if (copy_from_user(&cmd, user_buf, count)) {
retval = -EFAULT;
goto unlock_exit;
}
/* Prepare the buffer, noting the prefix */
memset(&buf, 0, sizeof(buf));
buf[0] = LAUNCHER_CTRL_COMMAND_PREFIX;
buf[1] = cmd;
/* The interrupt-in-endpoint handler also modifies dev->command. */
spin_lock(&dev->cmd_spinlock);
dev->command = cmd;
spin_unlock(&dev->cmd_spinlock);
pr_debug('Sending usb_control_message()n');
retval = usb_control_msg(dev->udev,
usb_sndctrlpipe(dev->udev, 0),
LAUNCHER_CTRL_REQUEST,
LAUNCHER_CTRL_REQUEST_TYPE,
LAUNCHER_CTRL_VALUE,
LAUNCHER_CTRL_INDEX,
&buf,
sizeof(buf),
HZ*5);
...
I won’t cover too much on the USB code as it is covered in far more depth in Matthias’ article and Greg KHs usb-skeleton.c in the kernel source tree.
To control our driver, we can send bytes from userspace to the /dev/launcher0 entry and I was able to repurpose Matthias’ launcher program to send the correct command codes to the device which I have added to the Git repository. When you create the executable and module using make, and assuming you have the right libraries, you will have a launcher_driver.ko and a launcher_control executable.
Insert the kernel module using insmod ./launcher_driver.ko
and you can then start issuing commands to the launcher.
Unfortunately by default, the device node /dev/launcher0 is created as root:root with permission 0600 which means that you can either chmod/chgrp it or run the launcher as root(!) which is the simplest test:
sudo ./launcher_control -f
usbhid and the Handsy Driver
As we’ve looked at, usbhid tries to claim our device by default due to it looking like a Human Interface Device and I had to do a fair amount of digging to find out how to stop usbhid from claiming our device which I’ll recap for those who seek enlightenment.
If you look in the source code for /drivers/usb/usbhid/hid-quirks.c
we can see a structure that defines a blacklist of HID devices.
static const struct hid_blacklist {
__u16 idVendor;
__u16 idProduct;
__u32 quirks;
} hid_blacklist[] = {
{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD },
...
{ USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS, HID_QUIRK_NOGET HID_QUIRK_MULTI_INPUT },
...
This shows a format of VENDOR_ID, DEVICE_ID and the HID_QUIRK mode which can be multiple values OR’d together and if we look further down the source, we can see the following function:
int usbhid_quirks_init(char **quirks_param)
{
u16 idVendor, idProduct;
u32 quirks;
int n = 0, m;
for (; n < MAX_USBHID_BOOT_QUIRKS && quirks_param[n]; n++) {
m = sscanf(quirks_param[n], '0x%hx:0x%hx:0x%x',
&idVendor, &idProduct, &quirks);
if (m != 3
usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
printk(KERN_WARNING
'Could not parse HID quirk module param %sn',
quirks_param[n]);
}
}
return 0;
}
Which is an array passed in from hid-core.c
in the following snippet:
/* Quirks specified at module load time */
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
MODULE_PARM_DESC(quirks, 'Add/modify USB HID quirks by specifying '
' quirks=vendorID:productID:quirks'
' where vendorID, productID, and quirks are all in'
' 0x-prefixed hex');
This shows us that the quirk param values are read in the form of 0xVENDORID:0xPRODUCTID:0xQUIRKS so now we know how to get usbhid to use a quirk for our device, we need to know which one.
Defined in include/linux/hid.h
are our quirks, we want to tell usbhid to just leave it alone so we’re going to use HID_QUIRK_IGNORE which has the value of 0x04.
Once we have this information we should be able to modprobe -r usbhid && modprobe usbhid quirks=0x2123:0x1010:0x04 … except that we can’t remove the module on our Fedora system…
[nick@slimtop ~]$ modprobe -r usbhid
FATAL: Module usbhid is builtin.
Oh dear. This means that the usbhid module isn’t loaded at runtime, instead it has been built into the kernel so we can’t change it from userspace as the kernel, with usbhid, is already running before the filesystem has been mounted so we will need to pass the parameter to the kernel in the form:module.parameter=OPTIONS
.
To do this, I edited my kernel command in /boot/grub2/grub.cfg to tell the builtin module about our device and add it to the quirks list.
...
echo 'Loading Fedora (3.6.2-4.fc17.i686)'
linux /vmlinuz-3.6.2-4.fc17.i686 root=/dev/mapper/vg_slimtop-lv_root ro rd.md=0 rd.dm=0 rd.lvm.lv=vg_slimtop/lv_root SYSFONT=True usbhid.quirks=0x2123:0x1010:0x04 KEYTABLE=uk rd.luks=0 rd.lvm.lv=vg_slimtop/lv_swap LANG=en_US.UTF-8 rhgb quiet
...
Finally, we can get our kernel module to load automatically by copying our launcher_driver.ko into the /lib/modules/`uname -r`/ directory and running depmod -a
.
On rebooting, you should be able to insert your launcher and up will pop a /dev/launcher0 without usbhid getting handsy with it.
udev
udev is the device manager for the Linux kernel. Primarily, it manages device nodes in /dev so we’re going to use that to manipulate the creation of our /dev/launcher0 entry.
There is a lot of information spread over the net on udev but it’s sometimes out of date and no longer relevant. It’s best to ask questions in IRC, copy existing rules (see /usr/lib/udev/rules.d/*
) or just read the source to get the most up-to-date information.
To avoid having to run our launcher as root we can create a special rule for udev[1] to manipulate how the node is created. To do this you will need to create a new file in /etc/udev/rules.d/10-dreamcheeky.rules
containing the following:
KERNEL'launcher?*',MODE='0666',GROUP='wheel'
This tells udev that we want any events from the KERNEL that match /dev/”launcher?*” which is what our driver requests (the regular expression matches any number after our launcher string, such as 1,2,3 etc).
When that event KEY has been matched, we apply the two ASSIGN values which specify the permissions mode, 0666 or rw-rw-rw-, and that it should belong to the wheel
group.
To test our rule, we need to know how udev sees our USB device so we do the following:
[nick@slimtop ~]$ find /sys/devices/ -name launcher0
/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0
To test our udev rule we run udevadm test
as follows against our device path
[nick@slimtop rules.d]$ sudo udevadm test /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0
run_command: calling: test
adm_test: version 182
This program is for debugging only, it does not run any program,
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.
builtin_kmod_init: load module index
...
parse_file: reading '/etc/udev/rules.d/10-dreamcheeky.rules' as rules file <--- OUR RULES FILE
...
parse_file: reading '/usr/lib/udev/rules.d/99-systemd.rules' as rules file
udev_rules_new: rules use 249228 bytes tokens (20769 * 12 bytes), 42425 bytes buffer
udev_rules_new: temporary index used 76720 bytes (3836 * 20 bytes)
udev_device_new_from_syspath: device 0xb9009810 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
udev_device_new_from_syspath: device 0xb9009b00 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
udev_device_read_db: device 0xb9009b00 filled with db file data
udev_rules_apply_to_event: GROUP 10 /etc/udev/rules.d/10-dreamcheeky.rules:1
udev_rules_apply_to_event: MODE 0666 /etc/udev/rules.d/10-dreamcheeky.rules:1
udev_device_new_from_syspath: device 0xb901a540 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0'
udev_device_new_from_syspath: device 0xb901a088 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2'
udev_device_new_from_syspath: device 0xb90239b0 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1'
udev_device_new_from_syspath: device 0xb9023cb0 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2'
udev_device_new_from_syspath: device 0xb9024000 has devpath '/devices/pci0000:00/0000:00:1d.0'
udev_device_new_from_syspath: device 0xb901f220 has devpath '/devices/pci0000:00'
udev_device_read_db: no db file to read /run/udev/data/+usb:2-1.2:1.0: No such file or directory
udev_node_add: handling device node '/dev/launcher0', devnum=c180:0, mode=0666, uid=0, gid=10
node_fixup: preserve permissions /dev/launcher0, 020666, uid=0, gid=10
node_symlink: preserve already existing symlink '/dev/char/180:0' to '../launcher0'
udev_device_update_db: created empty file '/run/udev/data/c180:0' for '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
ACTION=add
DEVNAME=/dev/launcher0
DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0
MAJOR=180
MINOR=0
SUBSYSTEM=usbmisc
UDEV_LOG=6
USEC_INITIALIZED=684838211
builtin_kmod_exit: unload module index
That’s it – you should be able to remove/insert your USB device and have /dev/launcher0 show up with the permissions and group you chose.
Conclusion
This has been a very long blog article but I hope this is helpful and serves to tie together a lot of good resources that seem spread thinly across the net so have fun and experiment!
The code for the userspace and driver is available on our Feabhas Github at https://github.com/feabhas/dreamlauncher with a single Makefile to build both projects.
I built this on Fedora 17 so your mileage may vary depending on your distribution/environment.
Dream Cheeky Drivers Review
[1] Special thanks go to the #udev IRC channel on freenode (in particular the user falconindy) and these two resource pages:
Dream Cheeky Drivers
- Introduction to the ARM® Cortex®-M7 Cache – Part 3 Optimising software to use cache - November 5, 2020
- Introduction to the ARM® Cortex®-M7 Cache – Part 2 Cache Replacement Policy - October 22, 2020
- Introduction to the ARM® Cortex®-M7 Cache – Part 1 Cache Basics - October 15, 2020