Last active
September 26, 2024 14:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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