aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2020-11-15 16:43:52 -0500
committerScott Murray <scott.murray@konsulko.com>2020-11-15 16:43:52 -0500
commit6295efdc2f60ea16f48962e7ffc4355dfbdfabf8 (patch)
tree0755e03eecb620ca9956843f9cf5b12b20d57feb
parente8fa8563692fcab198ebedf5f1fa2130b76b8507 (diff)
Changes: - Add can-dev-mapping-helper support changes from meta-agl-demo. It is less than ideal to duplicate them, but the timing of the planned layer reorganization and slimming down of meta-agl makes this seem less hassle. Once the shift to the new layout is done, things can be rationalized as necessary. - Also add the simple_can_simulator.py script, since without any physical CAN connection something is needed to drive testing. Bug-AGL: SPEC-3689 Signed-off-by: Scott Murray <scott.murray@konsulko.com> Change-Id: I655409806026024c804942415ac476e59b916c28
-rw-r--r--recipes-apis/agl-service-can-low-level/agl-service-can-low-level_git.bbappend10
-rw-r--r--recipes-apis/agl-service-can-low-level/can-dev-mapping-helper_1.0.bb21
-rw-r--r--recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.conf3
-rw-r--r--recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.service6
-rw-r--r--recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.sh36
-rwxr-xr-xrecipes-demo/simple-can-simulator/files/simple_can_simulator.py381
-rw-r--r--recipes-demo/simple-can-simulator/simple-can-simulator.bb15
-rw-r--r--recipes-platform/packagegroups/packagegroup-agl-telematics-demo-platform.bb1
8 files changed, 473 insertions, 0 deletions
diff --git a/recipes-apis/agl-service-can-low-level/agl-service-can-low-level_git.bbappend b/recipes-apis/agl-service-can-low-level/agl-service-can-low-level_git.bbappend
new file mode 100644
index 0000000..4f28210
--- /dev/null
+++ b/recipes-apis/agl-service-can-low-level/agl-service-can-low-level_git.bbappend
@@ -0,0 +1,10 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
+
+SRC_URI += "file://can-dev-mapping-helper.conf"
+
+do_install_append() {
+ install -D -m 0644 ${WORKDIR}/can-dev-mapping-helper.conf \
+ ${D}${sysconfdir}/systemd/system/afm-service-agl-service-can-low-level-.service.d/can-dev-mapping-helper.conf
+}
+
+RDEPENDS_${PN} += "can-dev-mapping-helper"
diff --git a/recipes-apis/agl-service-can-low-level/can-dev-mapping-helper_1.0.bb b/recipes-apis/agl-service-can-low-level/can-dev-mapping-helper_1.0.bb
new file mode 100644
index 0000000..37793fd
--- /dev/null
+++ b/recipes-apis/agl-service-can-low-level/can-dev-mapping-helper_1.0.bb
@@ -0,0 +1,21 @@
+SUMMARY = "Systemd unit for agl-service-can-low-level CAN device helper"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+inherit systemd allarch
+
+SRC_URI = "file://can-dev-mapping-helper.service \
+ file://can-dev-mapping-helper.sh \
+"
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+do_install() {
+ install -D -m 0644 ${WORKDIR}/${BPN}.service ${D}${systemd_system_unitdir}/${BPN}.service
+ install -D -m 0755 ${WORKDIR}/${BPN}.sh ${D}${sbindir}/${BPN}.sh
+}
+
+FILES_${PN} += "${systemd_system_unitdir}"
+
+RDEPENDS_${PN} += "bash"
diff --git a/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.conf b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.conf
new file mode 100644
index 0000000..c89cc4d
--- /dev/null
+++ b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.conf
@@ -0,0 +1,3 @@
+[Unit]
+Wants=can-dev-mapping-helper.service
+After=can-dev-mapping-helper.service
diff --git a/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.service b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.service
new file mode 100644
index 0000000..0436716
--- /dev/null
+++ b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=CAN interface helper
+
+[Service]
+ExecStart=/usr/sbin/can-dev-mapping-helper.sh
+Type=oneshot
diff --git a/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.sh b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.sh
new file mode 100644
index 0000000..c81014f
--- /dev/null
+++ b/recipes-apis/agl-service-can-low-level/files/can-dev-mapping-helper.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+#
+# Script to bring up CAN interfaces configured in /etc/dev-mapping.conf
+# as vcan interfaces if no physical interface is present.
+#
+
+vcan_up() {
+ if [ -n "$1" ]; then
+ echo "Bringing up $1 as virtual CAN device"
+ ip link add dev $1 type vcan
+ ip link set up $1
+ fi
+}
+
+if [ ! -f /etc/dev-mapping.conf ]; then
+ exit 0
+fi
+
+hs=$(grep ^hs= /etc/dev-mapping.conf |cut -d= -f2 |tr -d '"')
+ls=$(grep ^ls= /etc/dev-mapping.conf |cut -d= -f2 |tr -d '"')
+
+if [ -n "$hs" ]; then
+ echo "Checking $hs"
+ if ! ifconfig $hs >/dev/null 2>&1; then
+ vcan_up $hs
+ fi
+fi
+if [ -n "$ls" -a "$ls" != "$hs" ]; then
+ echo "Checking $ls"
+ if ! ifconfig $ls >/dev/null 2>&1; then
+ vcan_up $ls
+ fi
+fi
+
+exit 0
diff --git a/recipes-demo/simple-can-simulator/files/simple_can_simulator.py b/recipes-demo/simple-can-simulator/files/simple_can_simulator.py
new file mode 100755
index 0000000..83f8870
--- /dev/null
+++ b/recipes-demo/simple-can-simulator/files/simple_can_simulator.py
@@ -0,0 +1,381 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 Alex Bencz
+# Copyright (c) 2019 Konsulko Group, smurray@konsulko.com
+# Copyright (c) 2020 The Linux Foundation, jsmoeller@linuxfoundation.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+#
+# CANSocket from:
+#
+# https://github.com/abencz/python_socketcan/blob/master/python_socketcan_example.py
+#
+
+import sys
+import socket
+import argparse
+import struct
+import errno
+import threading
+import time
+
+class CANSocket(object):
+ FORMAT = "<IB3x8s"
+ FD_FORMAT = "<IB3x64s"
+
+ def __init__(self, interface=None):
+ self.sock = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
+ if interface is not None:
+ self.bind(interface)
+
+ def bind(self, interface):
+ self.sock.bind((interface,))
+ self.sock.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1)
+
+ def send(self, can_id, data, flags=0):
+ can_id = can_id | flags
+ can_pkt = struct.pack(self.FORMAT, can_id, len(data), data)
+ self.sock.send(can_pkt)
+
+ def sendfd(self, can_id, data, flags=0):
+ can_id = can_id | flags
+ datafd = data.ljust(64, b'\x00');
+ can_pkt = struct.pack(self.FD_FORMAT, can_id, len(datafd), datafd)
+ self.sock.send(can_pkt)
+
+ def recv(self, flags=0):
+ can_pkt = self.sock.recv(72)
+
+ if len(can_pkt) == 16:
+ can_id, length, data = struct.unpack(self.FORMAT, can_pkt)
+ else:
+ can_id, length, data = struct.unpack(self.FD_FORMAT, can_pkt)
+
+ can_id &= socket.CAN_EFF_MASK
+ return (can_id, data[:length])
+
+class VehicleSimulator(object):
+ DEFAULT_IDLE_RPM = 600
+
+ def __init__(self):
+ self.CRUISEMODE = False
+ self.CRUISEACTIVE = False
+ self.CRUISESPEED = 0
+ self.CRUISERPM = 0
+ self.freq = 10
+ self.vehicle_speed = 0
+ self.engine_speed = self.DEFAULT_IDLE_RPM
+ self.thread = threading.Thread(target=self.run, daemon=True)
+ self.lock = threading.Lock()
+
+ def reset(self):
+ with self.lock:
+ self.vehicle_speed = 0
+ self.engine_speed = self.DEFAULT_IDLE_RPM
+
+ def start(self):
+ self.thread.start()
+
+ def get_engine_speed(self):
+ with self.lock:
+ return int(self.engine_speed)
+
+ def get_vehicle_speed(self):
+ with self.lock:
+ return int(self.vehicle_speed)
+
+ def accelerate(self, target_speed, target_rpm, duration, bycruise = False):
+ if target_speed <= self.vehicle_speed:
+ return
+ v = (target_speed - self.vehicle_speed) / (duration * self.freq)
+ r = (target_rpm - self.engine_speed) / (duration * self.freq)
+ while self.vehicle_speed < target_speed and (not self.CRUISEACTIVE or bycruise):
+ with self.lock:
+ self.vehicle_speed += v;
+ self.engine_speed += r;
+ time.sleep(1 / self.freq)
+
+ def brake(self, target_speed, target_rpm, duration, bycruise = False):
+ if target_speed >= self.vehicle_speed:
+ return
+ v = (self.vehicle_speed - target_speed) / (duration * self.freq)
+ r = (self.engine_speed - target_rpm) / (duration * self.freq)
+ while self.vehicle_speed > target_speed and (not self.CRUISEACTIVE or bycruise):
+ with self.lock:
+ self.vehicle_speed -= v;
+ self.engine_speed -= r;
+ time.sleep(1 / self.freq)
+
+ def increase(self, bycruise = True):
+ if self.CRUISEACTIVE:
+ target_speed = self.vehicle_speed + 5
+ target_rpm = self.engine_speed * 1.1
+ self.accelerate(target_speed, target_rpm, 2, bycruise)
+
+ def decrease(self, bycruise = True):
+ if self.CRUISEACTIVE:
+ target_speed = self.vehicle_speed - 5
+ target_rpm = self.engine_speed * 0.9
+ self.brake(target_speed, target_rpm, 2, bycruise)
+
+ def resume(self, bycruise = True):
+ target_speed = self.CRUISESPEED
+ target_rpm = self.CRUISERPM
+ current_speed = self.get_vehicle_speed()
+ if target_speed > current_speed:
+ self.accelerate(target_speed, target_rpm, 2, bycruise)
+ else:
+ self.brake(target_speed, target_rpm, 2, bycruise)
+
+ def run(self):
+ while True:
+ if not self.CRUISEACTIVE:
+ self.accelerate(80, 3000, 5)
+ self.accelerate(104, 4000, 3)
+ self.brake(80, 3000, 3)
+ self.accelerate(104, 4000, 6)
+ self.brake(40, 2000, 4)
+ self.accelerate(90, 3000, 5)
+ self.brake(1, 650, 5)
+ if not self.CRUISEACTIVE:
+ self.reset()
+ time.sleep(5)
+
+class DiagnosticMessageHandler(object):
+ def __init__(self, can_sock, simulator, verbose=False):
+ self.can_sock = can_sock
+ self.simulator = simulator
+ self.verbose = verbose
+ self.thread = threading.Thread(target=self.run, daemon=True)
+
+ def start(self):
+ self.thread.start()
+
+ def run(self):
+ while True:
+ can_id, data = self.can_sock.recv()
+ #print('%03X#%s' % (can_id, ''.join(format(x, '02X') for x in data)))
+ if can_id == 0x7df:
+ # OBD-II request
+ if data[1] == 0x01 and data[2] == 0x0C:
+ # Engine speed
+ speed = self.simulator.get_engine_speed()
+ #print('engine speed = %d' % speed)
+ if speed > 16383.75:
+ speed = 16383.75
+ reply = [ 0x04, 0x41, 0x0C ]
+ reply.append(4 * speed // 256)
+ reply.append(4 * speed % 256)
+ # pad remaining bytes to make 8
+ reply.append(0)
+ reply.append(0)
+ reply.append(0)
+ self.can_sock.send(0x7e8, bytes(reply), 0)
+ elif data[1] == 0x01 and data[2] == 0x0D:
+ # Vehicle speed
+ speed = int(self.simulator.get_vehicle_speed()) % 256
+ #print('vehicle speed = %d' % speed)
+ reply = [ 0x03, 0x41, 0x0D ]
+ reply.append(speed)
+ # pad remaining bytes to make 8
+ reply.append(0)
+ reply.append(0)
+ reply.append(0)
+ reply.append(0)
+ self.can_sock.send(0x7e8, bytes(reply), 0)
+
+class SteeringWheelMessageHandler(object):
+ def __init__(self, can_sock, simulator, verbose=False):
+ self.can_sock = can_sock
+ self.simulator = simulator
+ self.verbose = verbose
+ self.thread = threading.Thread(target=self.run, daemon=True)
+ self.buttonpressed = False
+ self.buttonenabled = False
+ self.buttoncancel = False
+ self.buttondec = False
+ self.buttoninc = False
+ self.cruisemode = False
+ self.cruiseactive = False
+
+ def start(self):
+ self.thread.start()
+
+ def run(self):
+ while True:
+ can_id, data = self.can_sock.recv()
+ #print('%03X#%s' % (can_id, ''.join(format(x, '02X') for x in data)))
+ if can_id == 0x21:
+ #print('%03X#%s' % (can_id, ''.join(format(x, '02X') for x in data)))
+ if data:
+ #if data[6]:
+ #print('data6: %02X' % (data[6]))
+ if data[6] == 0x80 and not self.buttonpressed:
+ # we do skip any further lin messages
+ # two buttons at the same time won't work
+ # (aka unlikely w/o twisting fingers)
+ self.buttonpressed = True
+ self.buttonenabled = True
+ if data[6] == 0x08 and not self.buttonpressed:
+ self.buttonpressed = True
+ self.buttoncancel = True
+ if data[6] == 0x10 and not self.buttonpressed:
+ self.buttonpressed = True
+ self.buttondec = True
+ if data[6] == 0x40 and not self.buttonpressed:
+ self.buttonpressed = True
+ self.buttoninc = True
+ if data[6] == 0x00 and self.buttonpressed:
+ #now handle it as the button was released
+ if self.buttonenabled:
+ self.buttonenabled = False
+ self.cruisemode = not self.cruisemode
+ #print("set cruisemode to %s" % self.cruisemode)
+ self.simulator.CRUISEMODE = self.cruisemode
+ # disable/reset all if going off
+ if not self.cruisemode:
+ self.cruiseactive = False
+ self.simulator.CRUISEACTIVE = self.cruiseactive
+ self.simulator.CRUISESPEED = 0
+ self.simulator.CRUISERPM = 0
+ #print("set cruiseactive to %s" % self.cruiseactive)
+ if self.buttoncancel:
+ self.buttoncancel = False
+ self.simulator.CRUISESPEED = self.simulator.get_vehicle_speed()
+ self.simulator.CRUISERPM = self.simulator.get_engine_speed()
+ #print("set cruisespeed to %d" % self.simulator.CRUISESPEED )
+ #print("set cruiserpm to %d" % self.simulator.CRUISERPM )
+ self.cruiseactive = False
+ #print("set cruiseactive to %s" % self.cruiseactive )
+ self.simulator.CRUISEACTIVE = self.cruiseactive
+ if self.buttondec:
+ self.buttondec = False
+ if self.cruiseactive:
+ #print("decrease")
+ self.simulator.decrease()
+ else:
+ # set speed
+ #print("set speed")
+ self.simulator.CRUISESPEED = self.simulator.get_vehicle_speed()
+ self.simulator.CRUISERPM = self.simulator.get_engine_speed()
+ #print("set cruisespeed to %d" % self.simulator.CRUISESPEED )
+ #print("set cruiserpm to %d" % self.simulator.CRUISERPM )
+ self.cruiseactive = not self.cruiseactive
+ #print("set cruiseactive to %s" % self.cruiseactive )
+ self.simulator.CRUISEACTIVE = self.cruiseactive
+ if self.buttoninc:
+ self.buttoninc = False
+ if self.cruiseactive:
+ #print("increase")
+ self.simulator.increase()
+ else:
+ if self.simulator.CRUISESPEED > 0:
+ # resume
+ self.cruiseactive = not self.cruiseactive
+ self.simulator.CRUISEACTIVE = self.cruiseactive
+ #print("set cruiseactive to %s" % self.cruiseactive )
+ #print("resume")
+ self.simulator.resume()
+ self.buttonpressed = False
+
+
+class StatusMessageSender(object):
+ def __init__(self, can_sock, simulator, verbose=False):
+ self.can_sock = can_sock
+ self.simulator = simulator
+ self.verbose = verbose
+ self.thread = threading.Thread(target=self.run, daemon=True)
+
+ def start(self):
+ self.thread.start()
+
+ def run(self):
+ while True:
+ # Engine speed
+ speed = self.simulator.get_engine_speed()
+ if self.verbose:
+ print('engine speed = %d' % speed)
+ if speed > 16383.75:
+ speed = 16383.75
+ # Message is 1 byte unknown, 1 byte fuel level, 2 bytes engine speed (4x), fuel low @ bit 55
+ msg = [ 0, 0 ]
+ speed *= 4
+ msg.append(speed // 256)
+ msg.append(speed % 256)
+ # pad remaining bytes to make 8
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ self.can_sock.send(0x3d9, bytes(msg), 0)
+
+ # Vehicle speed
+ speed = int(self.simulator.get_vehicle_speed()) % 256
+ if self.verbose:
+ print('vehicle speed = %d' % speed)
+ # Message is 15 bits speed (64x), left aligned
+ msg = [ ]
+ # Note: extra 2x to yield required left-alignment
+ speed *= 128
+ msg.append(speed // 256)
+ msg.append(speed % 256)
+ # pad remaining bytes to make 8
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ msg.append(0)
+ self.can_sock.send(0x3e9, bytes(msg), 0)
+
+ # Sleep 100 ms
+ time.sleep(0.1)
+
+def main():
+ parser = argparse.ArgumentParser(description='Simple CAN vehicle simulator.')
+ parser.add_argument('interface', type=str, help='interface name (e.g. vcan0)')
+ parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
+ args = parser.parse_args()
+
+ try:
+ can_sock = CANSocket(args.interface)
+ diag_can_sock = CANSocket(args.interface)
+ steeringwheel_can_sock = CANSocket(args.interface)
+ except OSError as e:
+ sys.stderr.write('Could not listen on interface {0}\n'.format(args.interface))
+ sys.exit(e.errno)
+
+ print('Using {0}'.format(args.interface))
+ sim = VehicleSimulator()
+ status_sender = StatusMessageSender(can_sock, sim, args.verbose)
+ diag_handler = DiagnosticMessageHandler(diag_can_sock, sim, args.verbose)
+ steeringwheel_handler = SteeringWheelMessageHandler(steeringwheel_can_sock, sim, args.verbose)
+ sim.start()
+ status_sender.start()
+ diag_handler.start()
+ steeringwheel_handler.start()
+ try:
+ while True:
+ time.sleep(60)
+ except (KeyboardInterrupt, SystemExit):
+ #sim.stop()
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/recipes-demo/simple-can-simulator/simple-can-simulator.bb b/recipes-demo/simple-can-simulator/simple-can-simulator.bb
new file mode 100644
index 0000000..bca4487
--- /dev/null
+++ b/recipes-demo/simple-can-simulator/simple-can-simulator.bb
@@ -0,0 +1,15 @@
+DESCRIPTION = "Simulator that generates CAN messages of a driving car"
+LICENSE = "GPLv2"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0;md5=801f80980d171dd6425610833a22dbe6"
+
+SRC_URI = "file://simple_can_simulator.py"
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
+
+do_install() {
+ install -d ${D}${sbindir}
+ install -m 0755 ${WORKDIR}/simple_can_simulator.py ${D}${sbindir}
+}
+
+RDEPENDS_${PN} = "python3"
diff --git a/recipes-platform/packagegroups/packagegroup-agl-telematics-demo-platform.bb b/recipes-platform/packagegroups/packagegroup-agl-telematics-demo-platform.bb
index e3614de..0dbbd62 100644
--- a/recipes-platform/packagegroups/packagegroup-agl-telematics-demo-platform.bb
+++ b/recipes-platform/packagegroups/packagegroup-agl-telematics-demo-platform.bb
@@ -27,6 +27,7 @@ RDEPENDS_${PN}_append = " \
gpsd \
sw-gpsd-udev-conf \
usb-can-udev-conf \
+ simple-can-simulator \
${@bb.utils.contains('DISTRO_FEATURES', 'agl-devel', 'ofono-tests gps-utils' , '', d)} \
${AGL_APPS} \
${AGL_APIS} \