Tasmota/tasmota/xsns_89_mcp2515.ino

235 lines
7.0 KiB
Arduino
Raw Normal View History

/*
xsns_89_mcp2515.ino - MCP2515 CAN bus support for Tasmota
Copyright (C) 2021 Marius Bezuidenhout
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_SPI
#ifdef USE_MCP2515
/*********************************************************************************************\
* MCP2515 - Microchip CAN controller
*
* Connections:
* MCP2515 ESP8266 Tasmota
* ------- -------------- ----------
* INT not used
* SCK GPIO14 SPI CLK
* SI GPIO13 SPI MOSI
* SO GPIO12 SPI MISO
* CS GPIO0..5,15,16 MCP2515
* Gnd Gnd
* VCC Vin/5V
\*********************************************************************************************/
#define XSNS_89 89
#include "mcp2515.h"
// set defaults if not defined
#ifndef MCP2515_BITRATE
#define MCP2515_BITRATE CAN_500KBPS
#endif
#ifndef MCP2515_CLOCK
#define MCP2515_CLOCK MCP_8MHZ
#endif
2021-07-12 16:47:04 +01:00
#ifndef MCP2515_MAX_FRAMES
#define MCP2515_MAX_FRAMES 14
#endif
#ifndef MCP2515_BMS_CLIENT
#define MCP2515_BMS_CLIENT
// Look for Freedom Won BMS data in CAN message
#ifndef MCP2515_BMS_FREEDWON
#define MCP2515_BMS_FREEDWON
#endif // MCP2515_BMS_FREEDWON
#endif // MCP2515_BMS_CLIENT
#ifdef MCP2515_BMS_CLIENT
2021-07-12 16:47:04 +01:00
struct BMS_Struct {
uint16_t stateOfCharge;
uint16_t stateOfHealth;
float battVoltage;
2021-07-12 16:47:04 +01:00
float battAmp = 0;
float battTemp;
char name[17];
} bms;
#endif
int8_t mcp2515_init_status = 1;
2021-07-12 16:47:04 +01:00
struct can_frame canFrame;
MCP2515 *mcp2515 = nullptr;
char c2h(char c)
{
return "0123456789ABCDEF"[0x0F & (unsigned char)c];
}
void MCP2515_Init(void) {
mcp2515 = new MCP2515(5);
2021-07-12 16:47:04 +01:00
if (MCP2515::ERROR_OK != mcp2515->reset()) {
AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to reset module"));
mcp2515_init_status = 0;
}
2021-07-12 16:47:04 +01:00
if (MCP2515::ERROR_OK != mcp2515->setBitrate(MCP2515_BITRATE, MCP2515_CLOCK)) {
AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to set module bitrate"));
mcp2515_init_status = 0;
}
2021-07-12 16:47:04 +01:00
if (mcp2515_init_status && MCP2515::ERROR_OK != mcp2515->setNormalMode()) {
AddLog(LOG_LEVEL_ERROR, PSTR("MCP2515: Failed to set normal mode"));
mcp2515_init_status = 0;
}
}
void MCP2515_Read() {
uint8_t nCounter = 0;
2021-07-12 16:47:04 +01:00
bool checkRcv;
if(mcp2515_init_status) {
checkRcv = mcp2515->checkReceive();
while (checkRcv && nCounter <= MCP2515_MAX_FRAMES) {
mcp2515->checkReceive();
nCounter++;
if (mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
#ifdef MCP2515_BMS_CLIENT
#ifdef MCP2515_BMS_FREEDWON
2021-07-12 16:47:04 +01:00
switch(canFrame.can_id) {
// Charge/Discharge parameters
case 0x351:
break;
// State of Charge/Health
case 0x355:
if(6 >= canFrame.can_dlc) {
bms.stateOfCharge = (canFrame.data[1] << 8) + canFrame.data[0];
bms.stateOfHealth = (canFrame.data[3] << 8) + canFrame.data[2];
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x355"), canFrame.can_dlc);
}
break;
// Voltage/Current/Temperature
case 0x356:
if(6 >= canFrame.can_dlc) {
bms.battVoltage = (float)((canFrame.data[1] << 8) + canFrame.data[0])/100;
bms.battAmp = (float)((canFrame.data[3] << 8) + canFrame.data[2])/10;
bms.battTemp = ConvertTemp((float)((canFrame.data[5] << 8) + canFrame.data[4])/10); // Convert to fahrenheit if SetOpion8 is set
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Unexpected length (%d) for ID 0x356"), canFrame.can_dlc);
}
break;
// Battery / BMS name
case 0x370:
case 0x371:
for(int i = 0; i < canFrame.can_dlc; i++) {
uint8_t nameStrPos = i + ((canFrame.can_id & 0x1) * 8); // If can_id is 0x371 then fill from byte 8 onwards
bms.name[nameStrPos] = canFrame.data[i];
}
bms.name[16] = 0; // Ensure that name is null terminated
break;
// Modules status
case 0x372:
// Min. cell voltage id string
case 0x374:
// Min. cell temperature id string
case 0x376:
// Installed capacity
case 0x379:
// Serial number
case 0x380:
case 0x381:
break;
default:
char canMsg[17];
canMsg[0] = 0;
for(int i = 0; i < canFrame.can_dlc; i++) {
canMsg[i*2] = c2h(canFrame.data[i]>>4);
canMsg[i*2+1] = c2h(canFrame.data[i]);
}
if(canFrame.can_dlc > 0) {
canMsg[(canFrame.can_dlc - 1) * 2 + 2] = 0;
}
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Received message 0x%s from ID 0x%X"), canMsg, (uint32_t)canFrame.can_id);
break;
}
#endif // MCP2515_BMS_FREEDWON
#endif // MCP2515_BMS_CLIENT
2021-07-12 16:47:04 +01:00
} else if(mcp2515->checkError()) {
uint8_t errFlags = mcp2515->getErrorFlags();
mcp2515->clearRXnOVRFlags();
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP2515: Received error %d"), errFlags);
break;
}
}
}
}
void MCP2515_Show(bool Json) {
if (Json) {
#ifdef MCP2515_BMS_CLIENT
ResponseAppend_P(PSTR(",\"MCP2515\":{\"SOC\":%d,\"SOH\":%d}"), bms.stateOfCharge, bms.stateOfHealth);
#endif // MCP2515_BMS_CLIENT
#ifdef USE_WEBSERVER
} else {
#ifdef MCP2515_BMS_CLIENT
2021-07-12 16:47:04 +01:00
char ampStr[6];
dtostrf(bms.battAmp, 5, 1, ampStr);
WSContentSend_PD(HTTP_SNS_SOC, bms.name, bms.stateOfCharge);
WSContentSend_PD(HTTP_SNS_SOH, bms.name, bms.stateOfHealth);
WSContentSend_Voltage(bms.name, bms.battVoltage);
2021-07-12 16:47:04 +01:00
WSContentSend_PD(HTTP_SNS_CURRENT, ampStr);
WSContentSend_Temp(bms.name, bms.battTemp);
#endif // MCP2515_BMS_CLIENT
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns89(uint8_t function)
{
bool result = false;
if (PinUsed(GPIO_MCP2515_CS, GPIO_ANY) && TasmotaGlobal.spi_enabled) {
switch (function) {
case FUNC_INIT:
MCP2515_Init();
break;
2021-07-12 16:47:04 +01:00
case FUNC_EVERY_50_MSECOND:
MCP2515_Read();
break;
case FUNC_JSON_APPEND:
MCP2515_Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
MCP2515_Show(0);
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_MCP2515
#endif // USE_SPI