Getting weather data from the station to the raspberry

, par  olivier.wulveryck@gmail.com (Olivier Wulveryck) , popularité : 1%

Introduction

A bunch of friends/colleagues offered me a raspberry pi 3. It may become my VPN gateway, or my firewall, or the brain of my CCTV, or maybe the center of an alarm…. Maybe a spotify player…

Anyway, I have installed raspbian and I’m now playing with it.

Yesterday evening, as I was about to go to bed, I’ve had a very bad idea… I’ve linked together my RPI and my Oregon Weather Station. 3 hours later, I was still geeking…

As usual in the blog I will explain what I did, what did work, and what did not.

Attaching the devices

I’ve plugged the device, ok ! Now what does the system tells me about it :

What dmesg tells me is simply [ 2256.877522] usb 1-1.4 : new low-speed USB device number 5 using dwc_otg [ 2256.984860] usb 1-1.4 : New USB device found, idVendor=0fde, idProduct=ca01 [ 2256.984881] usb 1-1.4 : New USB device strings : Mfr=0, Product=1, SerialNumber=0 [ 2256.984894] usb 1-1.4 : Product : [ 2256.992719] hid-generic 0003:0FDE:CA01.0002 : hiddev0,hidraw0 : USB HID v1.10 Device [ ] on usb-3f980000.usb-1.4/input0

Finding the device

lsusb gives me the list of the usb devices on my rpi : # lsusb Bus 001 Device 004 : ID 0fde:ca01 Bus 001 Device 003 : ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002 : ID 0424:9514 Standard Microsystems Corp. Bus 001 Device 001 : ID 1d6b:0002 Linux Foundation 2.0 root hub

The first one correspond to my weather station but it belongs to root : # ls -lrt /dev/bus/usb/001/004 crw------- 1 root root 189, 3 Aug 30 12:45 /dev/bus/usb/001/004

Giving access : udev

The first thing to do is to allow access to my usb device so I won’t need to run any program as root. By default the pi user belongs to a bunch of groups. One of those is called plugdev. It is the one I will use for my experiment.

Get information about my Device

# udevadm info /dev/bus/usb/001/004 P : /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3 N : bus/usb/001/012 E : BUSNUM=001 E : DEVNAME=/dev/bus/usb/001/012 E : DEVNUM=012 E : DEVPATH=/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3 E : DEVTYPE=usb_device E : DRIVER=usb E : ID_BUS=usb E : ID_MODEL_ENC=\x20 E : ID_MODEL_FROM_DATABASE=WMRS200 weather station E : ID_MODEL_ID=ca01 E : ID_REVISION=0302 E : ID_SERIAL=0fde_ E : ID_USB_INTERFACES=:030000 : E : ID_VENDOR=0fde E : ID_VENDOR_ENC=0fde E : ID_VENDOR_FROM_DATABASE=Oregon Scientific E : ID_VENDOR_ID=0fde E : MAJOR=189 E : MINOR=11 E : PRODUCT=fde/ca01/302 E : SUBSYSTEM=__usb__ E : TYPE=0/0/0 E : USEC_INITIALIZED=5929384

I will note the vendor ID and the product ID. Funny stuff is that it presents itself as a WMRS200 and the model I have is a RMS300, but never mind.

Let’s create the udev rule file using the previous informations about the idVendor and the idProduct and create a special file /dev/weather-station. This will make the code more easy as I will be able to hard code the name, and leave the boring task of finding the device aside. cat << EOF > /etc/udev/rules.d/50-weather-station.rules # Weather Station SUBSYSTEM=="usb", ATTRSidVendor=="0fde", ATTRSidProduct=="ca01", MODE="0660", GROUP="plugdev", SYMLINK+="weather-station" EOF

Once done, I can restart udev with sudo /etc/init.d/udev restart or reload and trigger the rules with udevadm

IF something goes wrong, you can check the logs by turning the log level to info, reload the rules and look into the syslog file # udevadm control -l info # udevadm control -R # # grep -i udev /var/log/syslog # # ls -lrt /dev/weather-station lrwxrwxrwx 1 root root 15 Aug 29 21:32 /dev/weather-station -> bus/usb/001/007 # ls -lrt /dev/bus/usb/001/007 crw-rw-r— 1 root plugdev 189, 6 Aug 29 21:32 /dev/bus/usb/001/007

So far so good…

Accessing the data

The libusb

Linux has a low level library “libusb” that make the development of modules easy : libusb-1.0 . On my rpi, I can install the development version with a simple sudo apt-get install libusb-1.0-0-dev.

Using GO : The gousb library

A binding for the libusb is available through the gousb

There is also a lsusb version that is available as an example. Let’s grab it with a simple go get -v github.com/kylelemons/gousb/lsusb

and test it # GOPATH/bin/lsusb 001.004 0fde:ca01 WMRS200 weather station (Oregon Scientific) Protocol : (Defined at Interface level) Config 01 : -------------- Interface 00 Setup 00 Human Interface Device (No Subclass) None Endpoint 1 IN interrupt - unsynchronized data [8 0] -------------- 001.003 0424:ec00 SMSC9512/9514 Fast Ethernet Adapter (Standard Microsystems Corp.) Protocol : Vendor Specific Class Config 01 : -------------- Interface 00 Setup 00 Vendor Specific Class Endpoint 1 IN bulk - unsynchronized data [512 0] Endpoint 2 OUT bulk - unsynchronized data [512 0] Endpoint 3 IN interrupt - unsynchronized data [16 0] -------------- 001.002 0424:9514 SMC9514 Hub (Standard Microsystems Corp.) Protocol : Hub (Unused) TT per port Config 01 : -------------- Interface 00 Setup 00 Hub (Unused) Single TT Endpoint 1 IN interrupt - unsynchronized data [1 0] Interface 00 Setup 01 Hub (Unused) TT per port Endpoint 1 IN interrupt - unsynchronized data [1 0] -------------- 001.001 1d6b:0002 2.0 root hub (Linux Foundation) Protocol : Hub (Unused) Single TT Config 01 : -------------- Interface 00 Setup 00 Hub (Unused) Full speed (or root) hub Endpoint 1 IN interrupt - unsynchronized data [4 0] --------------

Rawread

I want to read the raw data from the device. The gousb package comes along with an example named “rawread”. I’m using it : # rawread git :(master) # go run main.go -device "0fde:ca01" 2016/08/30 14:00:01 Scanning for device "0fde:ca01"... Protocol : (Defined at Interface level) Config 01 : -------------- Interface 00 Setup 00 Human Interface Device (No Subclass) None Endpoint 1 IN interrupt - unsynchronized data [8 0] -------------- 2016/08/30 14:00:01 Connecting to endpoint... 2016/08/30 14:00:01 - &usb.DescriptorBus:0x1, Address:0x4, Spec:0x110, Device:0x302, Vendor:0xfde, Product:0xca01, Class:0x0, SubClass:0x0, Protocol:0x0, Configs :[]usb.ConfigInfousb.ConfigInfoConfig:0x1, Attributes:0x80, MaxPower:0x32, Interfaces :[]usb.InterfaceInfousb.InterfaceInfoNumber:0x0, Setups :[]usb.InterfaceSetupusb.InterfaceSetupNumber:0x0, Alternate:0x0, IfClass:0x3, IfSubClass:0x0, IfProtocol:0x0, Endpoints :[]usb.EndpointInfousb.EndpointInfoAddress:0x81, Attributes:0x3, MaxPacketSize:0x8, MaxIsoPacket:0x0, PollInterval:0xa, RefreshRate:0x0, SynchAddress:0x0

2016/08/30 14:00:01 open : usb : claim : libusb : device or resource busy [code -6]

After digging into the documentation and forums about the libusb, it looks like the device is locked by a generic kernel driver. So I need to detach it first.

The API call used to detach a kernel driver is libusb_detach_kernel_driver. Sadly it has not be bound to the golang’s library. Indeed Joseph Poirier maintain an active fork from the gousb library and he does implement the call. It’s a private method that is called implicitly by another call, so no need to modify the code from rawread to use it.

I’ve switched to his version : # go get github.com/jpoirier/gousb/rawread ./main -device "0fde:ca01" 2016/08/30 14:12:28 Scanning for device "0fde:ca01"... Protocol : (Defined at Interface level) Config 01 : -------------- Interface 00 Setup 00 Human Interface Device (No Subclass) None Endpoint 1 IN interrupt - unsynchronized data [8 0] -------------- 2016/08/30 14:12:28 Connecting to endpoint... 2016/08/30 14:12:28 - &usb.DescriptorBus:0x1, Address:0x4, Spec:0x110, Device:0x302, Vendor:0xfde, Product:0xca01, Class:0x0, SubClass:0x0, Protocol:0x0, Configs :[]usb.ConfigInfousb.ConfigInfoConfig:0x1, Attributes:0x80, MaxPower:0x32, Interfaces :[]usb.InterfaceInfousb.InterfaceInfoNumber:0x0, Setups :[]usb.InterfaceSetupusb.InterfaceSetupNumber:0x0, Alternate:0x0, IfClass:0x3, IfSubClass:0x0, IfProtocol:0x0, Endpoints :[]usb.EndpointInfousb.EndpointInfoAddress:0x81, Attributes:0x3, MaxPacketSize:0x8, MaxIsoPacket:0x0, PollInterval:0xa, RefreshRate:0x0, SynchAddress:0x0

Nothing more because the code ends by ep, err := dev.OpenEndpoint(uint8(*config), uint8(*iface), uint8(*setup), uint8(*endpoint)|uint8(usb.ENDPOINT_DIR_IN)) if err != nil log.Fatalf("open : %s", err) _ = ep

Cool… Now let’s add some code to read from the endpoint (which is an interface and that implements a Read method as described here ) b := make([]byte, 16) _, err = ep.Read(b) if err != nil log.Fatalf("read : %s", err) log.Printf("%v", b) _ = ep

And run the code : go run main.go -device "0fde:ca01" 2016/08/30 14:25:58 Scanning for device "0fde:ca01"... Protocol : (Defined at Interface level) Config 01 : -------------- Interface 00 Setup 00 Human Interface Device (No Subclass) None Endpoint 1 IN interrupt - unsynchronized data [8 0] -------------- 2016/08/30 14:25:58 Connecting to endpoint... 2016/08/30 14:25:58 - &usb.DescriptorBus:0x1, Address:0x4, Spec:0x110, Device:0x302, Vendor:0xfde, Product:0xca01, Class:0x0, SubClass:0x0, Protocol:0x0, Configs :[]usb.ConfigInfousb.ConfigInfoConfig:0x1, Attributes:0x80, MaxPower:0x32, Interfaces :[]usb.InterfaceInfousb.InterfaceInfoNumber:0x0, Setups :[]usb.InterfaceSetupusb.InterfaceSetupNumber:0x0, Alternate:0x0, IfClass:0x3, IfSubClass:0x0, IfProtocol:0x0, Endpoints :[]usb.EndpointInfousb.EndpointInfoAddress:0x81, Attributes:0x3, MaxPacketSize:0x8, MaxIsoPacket:0x0, PollInterval:0xa, RefreshRate:0x0, SynchAddress:0x0

2016/08/30 14:25:59 [7 0 48 0 48 53 1 255 7 255 0 66 129 239 0 32]

OK ! Here are the data, now what I need to figure out, is how to interpret them !

Decoding the Protocol

Internet is a great tool : I’ve found a description of the protocol here

I’ve read that it was mandatory to send a heartbeat sequence every 30 seconds. I will implement the heartbeat later. For now I will send it initially to request data from the station : // This is a hearbeat request (9 bytes array) h := []byte0x00, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 log.Println("Sending heartbeat") i, err := ep.Write(h) if err != nil log.Fatal("Cannot send heartbeat", err) log.Println("%v bytes sent",i)

And then read the stream back. Every data payload is separate from the others by a 0xffff sequence.

Testing the sequence initialization request

go run main.go -device "0fde:ca01" 2016/08/30 20:02:19 Scanning for device "0fde:ca01"... Protocol : (Defined at Interface level) Config 01 : -------------- Interface 00 Setup 00 Human Interface Device (No Subclass) None Endpoint 1 IN interrupt - unsynchronized data [8 0] -------------- 2016/08/30 20:02:19 Connecting to endpoint... 2016/08/30 20:02:19 Sending heartbeat 2016/08/30 20:02:19 heartbeat failed : usb : write : not an OUT endpoint

What did² I do wrong ?

PNG - 33 kio

Easy, I didn’t RTFM… Actually, I didn’t read the specification of the USB.

As described here the USB is a host-controlled bus which means that : Nothing on the bus happens without the host first initiating it. Devices cannot initiate a transaction. The USB is a Polled Bus The Host polls each device, requesting data or sending data

The possibles transaction are : IN : Device to Host OUT : Host to Device

On top of that, a device may handle 1 to N configuration which handles 1 to N endpoints which may be considered IN or OUT.

My weather station has only one endpoint which is IN. Therefore I will not be able to send information to the station from the host. What I will be able to send is a IN token to get data on the bus. # lsusb -v ... Endpoint Descriptor : bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10

Note I also see that the endpoint is an interrupt

To be continued…

This blog post is quiet long, and I haven’t finished my research yet. Indeed I think that there is enough information for the post to go live. I will post a part II as soon as I will have time to continue my experiments with the USB device and the rpi.

Voir en ligne : https://blog.owulveryck.info/2016/0...

Sites favoris Tous les sites

84 sites référencés dans ce secteur