Hi all,
I have a Raspberry Pi Nerves device which I have attached a MCP2515 SPI to CAN transceiver board. I can get this board to work fine with regular raspbian, and I just send commands using candump
and cansend
utilities from the canutils
tool suite. I’ve got to the point where I have a slightly customized buildroot image of the standard rpi3 firmware to enable the MCP2515 board and include the SocketCAN kernel modules (heavily based off of the work done by @brien and his 2018 talk titled “Customize Your Car: An Adventure in Using Elixir and Nerves to Hack Your Vehicle’s Electronics Network”. Huge thank you )
So far, so good. I was testing using the ng_can library, and indeed able to read the data. However, I have found the library to be a little bit less flexible than I need for my application. Particularly, it sets a fixed can bus bitrate at initialization. Additionally, the library hasn’t been updated in several years, and I’d like to avoid pulling in the C NIF dependencies. Plus, I’ve been looking for an excuse to finally create and publish my own library, and it is one that help me better cement my understanding of several OTP concepts.
So I’m thinking about trying to create a pure-elixir library for SocketCAN. I was browsing this thread on community automotive projects, which hints that this should be possible with just the :socket
module from Erlang.
Here’s where my trouble starts: I’ll be the first to admit that the Erlang documentation is daunting to me, having spent all my time in Elixir thus far. I can’t understand just quite why I can’t get the socket connection to the system SocketCAN to work, or if I am even approaching it in the right way. Maybe the problem is that it isn’t even supported on my hardware for some reason - though theoretically it should, since I have the kernel modules properly installed (ng_can
does work).
Some reference: the SocketCAN docs say that the socket connection can be opened in C with s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
. This is pretty much the same thing for the python-can
library implementation, which is sock = socket.socket(constants.PF_CAN, socket.SOCK_RAW, constants.CAN_RAW)
I think my main confusion is how to emulate these parameters in :socket.open/3
(though maybe I need :socket.open/4
?).
Below is a couple of things I’ve tried/some extra info.
Also, I tried to join the Elixir slack to be part of the Nerves community there, but I can’t find any valid join link. If someone could shoot one my way I would be grateful
Thank you!
Gus
iex(nerves@nerves.local)1> :socket.open(:local, :raw, :pf_can)
{:error, {:invalid, {:protocol, :pf_can}}}
iex(nerves@nerves.local)2> cat "/etc/protocols"
# Internet (IP) protocols
#
# Updated from http://www.iana.org/assignments/protocol-numbers and other
# sources.
ip 0 IP # internet protocol, pseudo protocol number
hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
egp 8 EGP # exterior gateway protocol
igp 9 IGP # any private interior gateway (Cisco)
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
hmp 20 HMP # host monitoring protocol
xns-idp 22 XNS-IDP # Xerox NS IDP
rdp 27 RDP # "reliable datagram" protocol
iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 [RFC905]
dccp 33 DCCP # Datagram Congestion Control Prot. [RFC4340]
xtp 36 XTP # Xpress Transfer Protocol
ddp 37 DDP # Datagram Delivery Protocol
idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport
ipv6 41 IPv6 # Internet Protocol, version 6
ipv6-route 43 IPv6-Route # Routing Header for IPv6
ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
idrp 45 IDRP # Inter-Domain Routing Protocol
rsvp 46 RSVP # Reservation Protocol
gre 47 GRE # General Routing Encapsulation
esp 50 IPSEC-ESP # Encap Security Payload [RFC2406]
ah 51 IPSEC-AH # Authentication Header [RFC2402]
skip 57 SKIP # SKIP
ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6
ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
rspf 73 RSPF CPHB # Radio Shortest Path First (officially CPHB)
vmtp 81 VMTP # Versatile Message Transport
eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco)
ospf 89 OSPFIGP # Open Shortest Path First IGP
ax.25 93 AX.25 # AX.25 frames
ipip 94 IPIP # IP-within-IP Encapsulation Protocol
etherip 97 ETHERIP # Ethernet-within-IP Encapsulation [RFC3378]
encap 98 ENCAP # Yet Another IP encapsulation [RFC1241]
# 99 # any private encryption scheme
pim 103 PIM # Protocol Independent Multicast
ipcomp 108 IPCOMP # IP Payload Compression Protocol
vrrp 112 VRRP # Virtual Router Redundancy Protocol [RFC5798]
l2tp 115 L2TP # Layer Two Tunneling Protocol [RFC2661]
isis 124 ISIS # IS-IS over IPv4
sctp 132 SCTP # Stream Control Transmission Protocol
fc 133 FC # Fibre Channel
mobility-header 135 Mobility-Header # Mobility Support for IPv6 [RFC3775]
udplite 136 UDPLite # UDP-Lite [RFC3828]
mpls-in-ip 137 MPLS-in-IP # MPLS-in-IP [RFC4023]
manet 138 # MANET Protocols [RFC5498]
hip 139 HIP # Host Identity Protocol
shim6 140 Shim6 # Shim6 Protocol [RFC5533]
wesp 141 WESP # Wrapped Encapsulating Security Payload
rohc 142 ROHC # Robust Header Compression
iex(nerves@nerves.local)3> :socket.supports()
[
ioctl_requests: [
siftxqlen: true,
sifmtu: true,
sifdstaddr: true,
sifbrdaddr: true,
sifaddr: true,
giftxqlen: true,
gifnetmask: true,
gifname: true,
gifmtu: true,
gifmap: true,
gifindex: true,
gifhwaddr: true,
gifflags: true,
gifdstaddr: true,
gifconf: true,
gifbrdaddr: true,
gifaddr: true,
sifflags: true
],
ioctl_flags: [
staticarp: false,
slave: true,
simplex: false,
renaming: false,
promisc: true,
ppromisc: false,
portsel: true,
pointopoint: true,
oactive: false,
notrailers: true,
noarp: true,
master: true,
lower_up: false,
link2: false,
link1: false,
link0: false,
knowsepoch: false,
echo: false,
dynamic: true,
dying: false,
dormant: false,
cantconfig: false,
automedia: true,
allmulti: true,
nogroup: false,
multicast: true,
up: true,
loopback: true,
broadcast: true,
debug: true,
running: true,
monitor: false
],
options: [
{{:ipv6, :authhdr}, true},
{{:ipv6, :use_min_mtu}, false},
{{:socket, :busy_poll}, false},
{{:IPv6, :pktoptions}, false},
{{:IP, :options}, false},
{{:IP, :transparent}, true},
{{:ipv6, :recvpktinfo}, true},
{{:socket, :acceptconn}, true},
{{:IP, :dontfrag}, false},
{{:IP, :recvif}, false},
{{:IPv6, :router_alert}, true},
{{:ip, :multicast_if}, true},
{{:IPv6, :recvtclass}, true},
{{:ip, :freebind}, true},
{{:socket, :domain}, true},
{{:socket, :reuseaddr}, true},
{{:ipv6, :portrange}, false},
{{:tcp, :maxseg}, true},
{{:IPv6, :drop_membership}, true},
{{:ipv6, :unicast_hops}, true},
{{:IP, :recvdstaddr}, false},
{{:tcp, :info}, false},
{{:IP, :multicast_if}, true},
{{:IP, :hdrincl}, true},
{{:ipv6, :faith}, false},
{{:IPv6, :tclass}, true},
{{:ipv6, :rthdr}, true},
{{:TCP, :maxseg}, true},
{{:ip, :sendsrcaddr}, false},
{{:ipv6, :addrform}, true},
{{:socket, :rxq_ovfl}, false},
{{:ipv6, :flowinfo}, false},
{{:ip, :dontfrag}, false},
{{:socket, :dontroute}, true},
{{:IPv6, :recvpktinfo}, true},
{{:IP, :tos}, true},
{{:ipv6, :recvhoplimit}, true},
{{:ipv6, :add_membership}, true},
{{:IPv6, :checksum}, false},
{{:IPv6, :rthdr}, true},
{{:IPv6, :dstopts}, true},
{{:ipv6, :multicast_if}, true},
{{:socket, :priority}, true},
{{:IPv6, :v6only}, true},
{{:ipv6, ...}, true},
{{...}, ...},
{...},
...
],
msg_flags: [
peek: true,
oob: true,
nosignal: true,
errqueue: true,
eor: true,
ctrunc: true,
confirm: true,
cmsg_cloexec: true,
dontroute: true,
trunc: true,
more: true
],
protocols: [
DCCP: true,
hopopt: true,
egp: true,
icmp: true,
EGP: true,
wesp: true,
udp: true,
RSPF: true,
HOPOPT: true,
skip: true,
CPHB: true,
vmtp: true,
"IPv6-Opts": true,
ROHC: true,
"mpls-in-ip": true,
RDP: true,
fc: true,
IPv6: true,
PUP: true,
PIM: true,
ipcomp: true,
IDRP: true,
igp: true,
GGP: true,
L2TP: true,
"IPSEC-ESP": true,
ah: true,
EIGRP: true,
sctp: true,
WESP: true,
"ipv6-icmp": true,
ISIS: true,
UDPLite: true,
"IPv6-NoNxt": true,
idrp: true,
"IPv6-ICMP": true,
ICMP: true,
ST: true,
TCP: true,
vrrp: true,
rsvp: true,
RSVP: true,
ggp: true,
"iso-tp4": true,
DDP: true,
...
],
sctp: false,
ipv6: true,
local: true,
netns: true,
sendfile: true
]
iex(nerves@nerves.local)4> :socket.open(29, :raw)
{:error, :eprotonosupport}
iex(nerves@nerves.local)5> "29 is the value defined for PF_CAN in socket.h"
"29 is the value defined for PF_CAN in socket.h"
iex(nerves@nerves.local)6> :socket.open(29, :raw, :local)
{:error, {:invalid, {:protocol, :local}}}
iex(nerves@nerves.local)7> :socket.open(29, :raw, :ip)
{:error, :eprotonosupport}
iex(nerves@nerves.local)8> :socket.open(29, :raw, :ipv6)
{:error, :einval}
iex(nerves@nerves.local)9> :socket.open(29, :raw, :tcp)
{:error, :eprotonosupport}
iex(nerves@nerves.local)10> :socket.open(29, :raw, :udp)
{:error, :einval}
iex(nerves@nerves.local)11>