Skip to content

Instantly share code, notes, and snippets.

@SilverCory
Last active September 26, 2024 14:18
Show Gist options
  • Save SilverCory/1eed0a84cb8675106358f90f73c42a97 to your computer and use it in GitHub Desktop.
Save SilverCory/1eed0a84cb8675106358f90f73c42a97 to your computer and use it in GitHub Desktop.
BMW F800GS CANBUS headlight detection for a lightbar. (double tap and the lightbar will come on). For arduino.
#include <SPI.h>
#include <mcp2515.h>
// DELAY_TIME the delay for the double click.
const unsigned long DELAY_TIME = (unsigned long)250;
// ID_SWITCH_CONTROL the message ID for a switch control.
const uint32_t ID_SWITCH_CONTROL = 0x130;
// MASK_HIGH_BEAM a bitmask for the highbeam toggle bits.
const byte MASK_HIGH_BEAM = 0b00000011;
// LIGHTBAR_PIN is the pin to turn on/off the lightbar.
const byte LIGHTBAR_PIN = 25;
// verbose will output tonnes of debugging to the serial log.
const bool verbose = false;
struct can_frame frame;
MCP2515 bus(15);
// on is whether the lightbar is on or not.
bool on = false;
// Step is an enum of steps to toggle on the light. (double flash)
// Steps should be executed in the order they're written in, defaulting back to STEP_OFF_PRIME.
enum class Step {
STEP_OFF_PRIME,
STEP_ON_PRIME,
STEP_OFF_TRIGGER,
STEP_ON_TRIGGER
};
// lastStepChangeTime is the time (from millis()) the last change of step was.
// Used to timeout the operation.
unsigned long lastStepChangeTime;
// current_step is the current state of the system.
Step current_step = Step::STEP_OFF_PRIME;
void setup() {
if (verbose) {
Serial.begin(115200);
while (!Serial);
Serial.println("Hello World");
}
pinMode(LIGHTBAR_PIN, OUTPUT);
digitalWrite(LIGHTBAR_PIN, LOW);
bus.reset();
bus.setBitrate(CAN_500KBPS, MCP_8MHZ);
bus.setNormalMode();
}
void loop() {
msg();
if (bus.readMessage(&frame) == MCP2515::ERROR_OK) {
switchControl();
serialDataOutput();
}
if (!on) {
digitalWrite(LIGHTBAR_PIN, LOW);
}
}
void changeState(Step newState) {
if (verbose && newState != current_step) {
Serial.print("NEW STEP: ");
Serial.print((int)current_step);
Serial.print(" --> ");
Serial.println((int)newState);
}
current_step = newState;
lastStepChangeTime = millis();
}
// timeoutReset will ensure that the double click will timeout.
void timeoutReset(unsigned long newTime) {
if (newTime - lastStepChangeTime > DELAY_TIME) {
current_step = Step::STEP_OFF_PRIME;
}
}
// switchControl is uhhhh...
void switchControl() {
// Ensure message is switch control.
if (frame.can_id != ID_SWITCH_CONTROL) return;
unsigned long newTime = millis();
timeoutReset(newTime);
if ((MASK_HIGH_BEAM & frame.data[6]) == 0b00000001) { // HighBeam is on.
switch (current_step) {
case Step::STEP_OFF_PRIME:
current_step = Step::STEP_ON_PRIME;
lastStepChangeTime = newTime;
break;
case Step::STEP_ON_PRIME:
changeState(Step::STEP_ON_PRIME);
break;
case Step::STEP_OFF_TRIGGER:
case Step::STEP_ON_TRIGGER:
changeState(Step::STEP_ON_TRIGGER);
on = true;
digitalWrite(LIGHTBAR_PIN, HIGH);
break;
}
} else { // HighBeam is off
switch (current_step) {
case Step::STEP_ON_PRIME:
changeState(Step::STEP_OFF_TRIGGER);
break;
case Step::STEP_OFF_TRIGGER:
break;
default:
changeState(Step::STEP_OFF_PRIME);
break;
}
on = false;
digitalWrite(LIGHTBAR_PIN, LOW);
}
}
// ==================================================== TESTING ======================================================= //
/*
Testing should use bus.setLoopbackMode();
The writeTest method will send a test switch frame every 500 millis.
*/
unsigned long msgPrime;
// output a "hello" message every 5 seconds.
void msg() {
if (!verbose) return;
unsigned long newTime = millis();
if (newTime - msgPrime < 5000) {
return;
}
Serial.println("hello");
msgPrime = newTime;
}
void serialDataOutput() {
if (!verbose) return;
// if(frame.can_id != 0x130) return; // A filter for this.
Serial.print("0x");
Serial.print(frame.can_id, HEX); // print ID
Serial.print(" ");
Serial.print(frame.can_dlc, DEC); // print DLC
Serial.print(" ");
for (int i = 0; i < frame.can_dlc; i++) { // print the data
Serial.print(frame.data[i], BIN);
Serial.print(" ");
}
Serial.println();
}
// doShee makes the dashboard into a christmas display of lights O_O?...
// The aim was to make the left indicator stay solid...
void doShee() {
// 130 8 11111111 11101111 11111111 11111111 11111111 11111111 1011010 1010111
// 130 8 11111111 11101111 11111111 11111111 11111111 11111111 1011010 1001111
struct can_frame frame;
frame.can_id = 0x130;
frame.can_dlc = 6;
frame.data[0] = 0b11111111;
frame.data[1] = 0b11101111;
frame.data[2] = 0b11111111;
frame.data[3] = 0b11111111;
frame.data[4] = 0b11111111;
frame.data[5] = 0b11111111;
frame.data[6] = 0b1011010;
frame.data[7] = 0b1010111;
/* send out the message to the bus and
tell other devices this is a standard frame from 0x00. */
bus.sendMessage(&frame);
}
unsigned long testPrime;
unsigned int itr;
void writeTest() {
unsigned long newTime = millis();
if (newTime - testPrime < 700) {
return;
}
testPrime = newTime;
struct can_frame frame;
frame.can_id = ID_SWITCH_CONTROL;
frame.can_dlc = 6;
frame.data[0] = 0x00;
frame.data[1] = 0x00;
frame.data[2] = 0x00;
frame.data[3] = 0x00;
frame.data[4] = 0x00;
switch (itr++) {
case 3:
case 4:
case 5:
frame.data[5] = 0b00000001;
break;
case 0:
case 1:
case 2:
default:
frame.data[5] = 0b00000010;
break;
}
if (itr >= 5) {
Serial.println("\n\n\n\n\n\n\n");
itr = 0;
}
/* send out the message to the bus and
tell other devices this is a standard frame from 0x00. */
bus.sendMessage(&frame);
}
/*
* The aim of this is to try to detect changes in messages.
* A change in a message should correlate to an action/operation performed on the vehicle.
*/
import processing.serial.*;
import java.lang.IllegalArgumentException;
import java.lang.System;
import java.util.Map;
Serial myPort; // Create object from Serial class
PrintWriter output;
Map<Integer, Frame> historyMap = new HashMap<Integer, Frame>();
void setup()
{
String portName = Serial.list()[1];
myPort = new Serial(this, portName, 115200);
output = createWriter("bus_decode.txt");
}
void draw() {
while (myPort.available() > 0) {
readFrame();
}
}
void readFrame() {
String inBuffer = myPort.readString();
if (inBuffer == null || !inBuffer.startsWith("0x")) return;
Frame inFrame = new Frame(inBuffer);
if (!historyMap.containsKey(inFrame.frameId)) {
historyMap.put(inFrame.frameId, inFrame);
return; // first entry for frameId.
}
Frame compareFrame = historyMap.get(inFrame.frameId);
if (!inFrame.equals(compareFrame)) {
historyMap.put(inFrame.frameId, inFrame);
System.out.println(inFrame.toString());
output.println(inFrame.toString());
output.flush();
}
}
static class Frame {
int frameId;
int len;
int[] data;
Frame(String input) throws IllegalArgumentException {
if (!input.startsWith("0x")) throw new IllegalArgumentException();
input = input.substring(2);
String[] parts = input.split(" ");
if (parts.length < 3) throw new IllegalArgumentException();
this.frameId = Integer.parseInt(parts[0], 16);
this.len = Integer.parseInt(parts[1]);
this.data = new int[this.len];
for (int i = 2; i < this.len+2; i++) {
this.data[i-2] = Integer.parseInt(parts[i], 2);
}
}
@Override
public boolean equals(Object fObj) {
if (!(fObj instanceof Frame)) return false;
Frame frame = (Frame) fObj;
if (frame.frameId != this.frameId || frame.len != this.len) return false;
for (int i = 0; i < frame.data.length; i++) {
if (frame.data[i] != this.data[i]) return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Frame(\n")
.append("\tFrame ID:\t").append("0x").append(Integer.toHexString(this.frameId)).append("\n")
.append("\tLength:\t").append(this.len).append("\n")
.append("\tdata:\t");
for (int d : this.data) {
sb.append(Integer.toBinaryString(d)).append(" ");
}
sb.append("\n)");
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment