mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-25 19:56:30 +00:00
Add support for ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses
- Bump version V14.4.1.2
This commit is contained in:
parent
5f95b33220
commit
1a482364bc
20
CHANGELOG.md
20
CHANGELOG.md
@ -3,7 +3,20 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased] - Development
|
## [Unreleased] - Development
|
||||||
|
|
||||||
## [14.4.1.1]
|
## [14.4.1.2]
|
||||||
|
### Added
|
||||||
|
- Support for ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses
|
||||||
|
|
||||||
|
### Breaking Changed
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
|
||||||
|
## [14.4.1.1] 20241231
|
||||||
### Added
|
### Added
|
||||||
- Command ``SetOption163 1`` to disable display of Device name in GUI header
|
- Command ``SetOption163 1`` to disable display of Device name in GUI header
|
||||||
- Berry `animate.crenel` primitive (#22673)
|
- Berry `animate.crenel` primitive (#22673)
|
||||||
@ -15,8 +28,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Berry driver for M5Stack 8encoder (#22724)
|
- Berry driver for M5Stack 8encoder (#22724)
|
||||||
- Support for PCF85063 RTC (#22727)
|
- Support for PCF85063 RTC (#22727)
|
||||||
|
|
||||||
### Breaking Changed
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- ESP32 disable PSRAM check (and on restart some relay toggles) with `#define DISABLE_PSRAMCHECK true` (#21266)
|
- ESP32 disable PSRAM check (and on restart some relay toggles) with `#define DISABLE_PSRAMCHECK true` (#21266)
|
||||||
- TLS disable ECDSA for MQTT to ensure we don't break fingerprints after #22649
|
- TLS disable ECDSA for MQTT to ensure we don't break fingerprints after #22649
|
||||||
@ -32,9 +43,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- LVGL updated `Antiburn.tapp` (#22699)
|
- LVGL updated `Antiburn.tapp` (#22699)
|
||||||
- Matter Air Quality sensor (#22708)
|
- Matter Air Quality sensor (#22708)
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
|
|
||||||
## [Released]
|
## [Released]
|
||||||
|
|
||||||
## [14.4.1] 20241215
|
## [14.4.1] 20241215
|
||||||
|
@ -114,10 +114,11 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
|
|||||||
|
|
||||||
[Complete list](BUILDS.md) of available feature and sensors.
|
[Complete list](BUILDS.md) of available feature and sensors.
|
||||||
|
|
||||||
## Changelog v14.4.1.1
|
## Changelog v14.4.1.2
|
||||||
### Added
|
### Added
|
||||||
- Command ``SetOption163 1`` to disable display of Device name in GUI header
|
- Command ``SetOption163 1`` to disable display of Device name in GUI header
|
||||||
- Support for PCF85063 RTC [#22727](https://github.com/arendst/Tasmota/issues/22727)
|
- Support for PCF85063 RTC [#22727](https://github.com/arendst/Tasmota/issues/22727)
|
||||||
|
- Support for ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses
|
||||||
- Berry `animate.crenel` primitive [#22673](https://github.com/arendst/Tasmota/issues/22673)
|
- Berry `animate.crenel` primitive [#22673](https://github.com/arendst/Tasmota/issues/22673)
|
||||||
- Berry scroll to Leds_matrix [#22693](https://github.com/arendst/Tasmota/issues/22693)
|
- Berry scroll to Leds_matrix [#22693](https://github.com/arendst/Tasmota/issues/22693)
|
||||||
- Berry add unicode encoding to string parsing [#22713](https://github.com/arendst/Tasmota/issues/22713)
|
- Berry add unicode encoding to string parsing [#22713](https://github.com/arendst/Tasmota/issues/22713)
|
||||||
|
@ -532,6 +532,7 @@ const char kSensorNamesFixed[] PROGMEM =
|
|||||||
#define MAX_BL0906_RX 6 // Model number of phases, 2 (EM2), 6 (EM6)
|
#define MAX_BL0906_RX 6 // Model number of phases, 2 (EM2), 6 (EM6)
|
||||||
#define MAX_BL0942_RX 8 // Baudrates 1/5 (4800), 2/6 (9600), 3/7 (19200), 4/8 (38400), Support Positive values only 1..4, Support also negative values 5..8
|
#define MAX_BL0942_RX 8 // Baudrates 1/5 (4800), 2/6 (9600), 3/7 (19200), 4/8 (38400), Support Positive values only 1..4, Support also negative values 5..8
|
||||||
#define MAX_CSE7761 2 // Model 1/2 (DUALR3), 2/2 (POWCT)
|
#define MAX_CSE7761 2 // Model 1/2 (DUALR3), 2/2 (POWCT)
|
||||||
|
#define MAX_TWAI SOC_TWAI_CONTROLLER_NUM
|
||||||
|
|
||||||
const uint16_t kGpioNiceList[] PROGMEM = {
|
const uint16_t kGpioNiceList[] PROGMEM = {
|
||||||
GPIO_NONE, // Not used
|
GPIO_NONE, // Not used
|
||||||
@ -1139,11 +1140,16 @@ const uint16_t kGpioNiceList[] PROGMEM = {
|
|||||||
#ifdef USE_WOOLIIS
|
#ifdef USE_WOOLIIS
|
||||||
AGPIO(GPIO_WOOLIIS_RX), // Wooliis Battery capacity monitor Serial interface
|
AGPIO(GPIO_WOOLIIS_RX), // Wooliis Battery capacity monitor Serial interface
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
#ifdef USE_ESP32_TWAI
|
#ifdef USE_ESP32_TWAI
|
||||||
AGPIO(GPIO_TWAI_TX), // ESP32 TWAI serial interface
|
#if SOC_TWAI_SUPPORTED
|
||||||
AGPIO(GPIO_TWAI_RX),
|
AGPIO(GPIO_TWAI_TX) + AGMAX(MAX_TWAI), // ESP32 TWAI serial interface
|
||||||
AGPIO(GPIO_TWAI_BO),
|
AGPIO(GPIO_TWAI_RX) + AGMAX(MAX_TWAI),
|
||||||
AGPIO(GPIO_TWAI_CLK),
|
AGPIO(GPIO_TWAI_BO) + AGMAX(MAX_TWAI),
|
||||||
|
AGPIO(GPIO_TWAI_CLK) + AGMAX(MAX_TWAI),
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------------*\
|
/*-------------------------------------------------------------------------------------------*\
|
||||||
|
@ -22,6 +22,6 @@
|
|||||||
|
|
||||||
#define TASMOTA_SHA_SHORT // Filled by Github sed
|
#define TASMOTA_SHA_SHORT // Filled by Github sed
|
||||||
|
|
||||||
const uint32_t TASMOTA_VERSION = 0x0E040101; // 14.4.1.1
|
const uint32_t TASMOTA_VERSION = 0x0E040102; // 14.4.1.2
|
||||||
|
|
||||||
#endif // _TASMOTA_VERSION_H_
|
#endif // _TASMOTA_VERSION_H_
|
||||||
|
@ -1,20 +1,37 @@
|
|||||||
/*
|
/*
|
||||||
xdrv_91_esp32_twai.ino - ESP32 TWAI support for Tasmota
|
xdrv_91_esp32_twai.ino - ESP32 TWAI support for Tasmota
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2024 Theo Arends
|
SPDX-FileCopyrightText: 2025 Theo Arends
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-3.0-only
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#ifdef USE_ESP32_TWAI
|
#ifdef USE_ESP32_TWAI
|
||||||
|
#if SOC_TWAI_SUPPORTED
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) - needs external tranceiver
|
* ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses.
|
||||||
|
* - Supports all TWAI busses provided by different ESP32 architectures.
|
||||||
|
* - Needs external tranceiver like M5Unit Mini CAN (not isolated!) or M5Unit CANBus (isolated).
|
||||||
*
|
*
|
||||||
* The TWAI controller is not compatible with ISO11898-1 FD Format frames, and will interpret such frames as errors.
|
* The TWAI controller is not compatible with ISO11898-1 FD Format frames, and will interpret
|
||||||
* CAN bus message format is 32-bit identifier and up to 8 bytes of data
|
* such frames as errors. CAN bus message format is 32-bit identifier and up to 8 bytes of data
|
||||||
*
|
*
|
||||||
* You'll needs a berry driver with class "twai" and functions "config" and "decode" as shown below
|
* Due to the CAN-bus nature of many received datagrams you'll needs a berry driver with
|
||||||
|
* class "twai" and functions "config" and "decode".
|
||||||
|
* - See example below.
|
||||||
|
* - When executed by `preinit.be` it allows to configure the TWAI driver mode and speed
|
||||||
|
*
|
||||||
|
* Supported command:
|
||||||
|
* TwaiSend[<bus>] <identifier>[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]
|
||||||
|
* TwaiSend[<bus>] {"ID":<identifier>,["DATA":[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]]}
|
||||||
|
* - An <identifier> with bit 31 set (0x80000000) is a 29-bit identifier
|
||||||
|
* - All <data> is optional
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* TwaiSend1 0x481,0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21
|
||||||
|
* TwaiSend1 {"ID":0x481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
|
||||||
|
* TwaiSend2 {"ID":0x80000481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
#define XDRV_91 91
|
#define XDRV_91 91
|
||||||
@ -37,10 +54,11 @@ const char kTwaiSpeeds[] PROGMEM = "25K|50K|100K|125K|250K|500K|800K|1M";
|
|||||||
const char kTwaiModes[] PROGMEM = "Normal|No Ack|Listen Only";
|
const char kTwaiModes[] PROGMEM = "Normal|No Ack|Listen Only";
|
||||||
|
|
||||||
struct TWAIs {
|
struct TWAIs {
|
||||||
twai_mode_t mode;
|
twai_handle_t bus[MAX_TWAI];
|
||||||
uint8_t speed;
|
twai_mode_t mode[MAX_TWAI];
|
||||||
|
uint8_t speed[MAX_TWAI];
|
||||||
|
bool installed[MAX_TWAI];
|
||||||
bool supported;
|
bool supported;
|
||||||
bool installed;
|
|
||||||
} Twai;
|
} Twai;
|
||||||
|
|
||||||
#ifdef USE_BERRY
|
#ifdef USE_BERRY
|
||||||
@ -51,69 +69,105 @@ struct TWAIs {
|
|||||||
/*
|
/*
|
||||||
file twai.be contents:
|
file twai.be contents:
|
||||||
|
|
||||||
tasmota.remove_driver(twai)
|
|
||||||
|
|
||||||
class twai_cls
|
class twai_cls
|
||||||
var active # (bool)
|
var active, pressure_next # (bool, bool)
|
||||||
var twai_speed, twai_mode # (int, int)
|
var twai_speed, twai_mode # (int, int)
|
||||||
var am012_status, am014_substatus, am024_power # (int, int, int)
|
var am012_status, am014_substatus, am024_power # (int, int, int)
|
||||||
var pressure, setpoint, flow_temp # (float, float, float)
|
var dz_am012_status, dz_am014_substatus # (int, int)
|
||||||
|
var pressure, setpoint, flow_temp # (float, float, float)
|
||||||
|
var dz_pressure, dz_flow_temp # (float, float)
|
||||||
|
|
||||||
def init()
|
def init()
|
||||||
self.active = 0
|
|
||||||
self.twai_speed = 7 # 0 = 25K, 1 = 50K, 2 = 100K, 3 = 125K, 4 = 250K, 5 = 500K, 6 = 800K, 7 = 1Mbits
|
self.twai_speed = 7 # 0 = 25K, 1 = 50K, 2 = 100K, 3 = 125K, 4 = 250K, 5 = 500K, 6 = 800K, 7 = 1Mbits
|
||||||
self.twai_mode = 2 # 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
|
self.twai_mode = 2 # 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
|
||||||
|
self.active = 0
|
||||||
self.am012_status = 0
|
self.am012_status = 0
|
||||||
self.am014_substatus = 0
|
self.am014_substatus = 0
|
||||||
|
self.dz_am012_status = 0
|
||||||
|
self.dz_am014_substatus = 0
|
||||||
self.am024_power = 0
|
self.am024_power = 0
|
||||||
|
self.pressure_next = 0
|
||||||
self.pressure = 0
|
self.pressure = 0
|
||||||
|
self.dz_pressure = 0
|
||||||
self.setpoint = 0
|
self.setpoint = 0
|
||||||
self.flow_temp = 0
|
self.flow_temp = 0
|
||||||
|
self.dz_flow_temp = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def config()
|
def config(bus)
|
||||||
return self.twai_mode << 3 | self.twai_speed
|
# if bus != 1 return nil end # Exit if not my bus
|
||||||
|
return self.twai_mode << 3 | self.twai_speed # Initial configure TWAI driver
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode(ident, data1, data2)
|
def decode(bus, ident, data1, data2)
|
||||||
var id = ident & 0xfff
|
# if bus != 1 return nil end # Exit if not my bus
|
||||||
if id == 0x076
|
var id = ident & 0x7ff # Remeha uses 11 bit Standard Frame Format
|
||||||
|
if id == 0x076 # Incremental counter from 0 to 255
|
||||||
# tasmota.log(f"RMH: 0x{id:03x} Count {data1}", 3)
|
# tasmota.log(f"RMH: 0x{id:03x} Count {data1}", 3)
|
||||||
elif id == 0x100
|
elif id == 0x100 # Date and Time
|
||||||
var time = 441763200 + (data2 * 24 * 60 * 60) + (data1 / 1000)
|
var epoch = 441763200 + (data2 * 24 * 60 * 60) + (data1 / 1000)
|
||||||
# tasmota.log(f"RMH: 0x{id:03x} Time {tasmota.time_str(time)}", 3)
|
# tasmota.log(f"RMH: 0x{id:03x} Time {tasmota.time_str(epoch)}", 3)
|
||||||
elif id == 0x1C1
|
elif id == 0x1C1 # Many different data1/2
|
||||||
if data1 & 0x3ff == 0x300
|
if data1 & 0x00ffffff == 0x503f41 # Next time it's pressure
|
||||||
self.pressure = (data2 & 0xff00) / 2560.0
|
self.pressure_next = 1
|
||||||
|
elif self.pressure_next == 1
|
||||||
|
self.pressure = (data2 & 0xff00)/2560.0 # This must be pressure
|
||||||
|
self.pressure_next = 0
|
||||||
end
|
end
|
||||||
elif id == 0x382
|
elif id == 0x382
|
||||||
self.am024_power = data1 & 0xff # Relative power
|
self.am024_power = data1 & 0xff # Relative power
|
||||||
self.setpoint = (data1 & 0xffff00)/25600.0 # Setpoint
|
self.setpoint = (data1 & 0xffff00)/25600.0 # Setpoint
|
||||||
# tasmota.log(f"RMH: 0x{id:03x} Busy {self.am024_power}%, Setpoint {self.setpoint}", 3)
|
# tasmota.log(f"RMH: 0x{id:03x} Busy {self.am024_power}%, Setpoint {self.setpoint}", 3)
|
||||||
elif id == 0x282
|
elif id == 0x282
|
||||||
self.flow_temp = (data1 & 0xffff00)/25600.0
|
self.flow_temp = (data1 & 0xffff00)/25600.0
|
||||||
# tasmota.log(f"RMH: 0x{id:03x} DHW temp {self.flow_temp}", 3)
|
# tasmota.log(f"RMH: 0x{id:03x} DHW temp {self.flow_temp}", 3)
|
||||||
elif id == 0x481
|
elif id == 0x481 # Status information
|
||||||
self.am012_status = data1 & 0xff
|
self.am012_status = data1 & 0xff
|
||||||
self.am014_substatus = (data1 & 0xff00)/256
|
self.am014_substatus = (data1 & 0xff00)/256
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.active = 1 # At least one valid decode
|
self.active = 1 # At least one valid decode
|
||||||
end
|
end
|
||||||
|
|
||||||
#- add sensor value to teleperiod -#
|
#- add sensor value to teleperiod -#
|
||||||
def json_append()
|
def json_append()
|
||||||
if !self.active return nil end # Exit if never decoded something
|
if !self.active return nil end # Exit if never decoded something
|
||||||
import string
|
import string
|
||||||
var msg = string.format(",\"Calenta\":{\"AM012\":%i,\"AM014\":%i,\"Pressure\":%.1f,\"Setpoint\":%.1f,\"Flow\":%.1f}",
|
var msg = string.format(",\"Calenta\":{\"AM012\":%i,\"AM014\":%i,\"Pressure\":%.1f,\"Setpoint\":%.1f,\"Flow\":%.1f}",
|
||||||
self.am012_status, self.am014_substatus, self.pressure, self.setpoint, self.flow_temp)
|
self.am012_status, self.am014_substatus, self.pressure, self.setpoint, self.flow_temp)
|
||||||
tasmota.response_append(msg)
|
tasmota.response_append(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#- perform action just after teleperiod -#
|
||||||
|
# def after_teleperiod()
|
||||||
|
#- perform action every second -#
|
||||||
|
def every_second()
|
||||||
|
# Only send if changed to reduce TWAI wait time
|
||||||
|
if self.dz_pressure != self.pressure
|
||||||
|
tasmota.cmd('_DzSend1 523,' .. self.pressure) # Send pressure to Domoticz
|
||||||
|
end
|
||||||
|
self.dz_pressure = self.pressure
|
||||||
|
|
||||||
|
if self.dz_flow_temp != self.flow_temp
|
||||||
|
tasmota.cmd('_DzSend1 526,' .. self.flow_temp) # Send flow temp to Domoticz
|
||||||
|
end
|
||||||
|
self.dz_flow_temp = self.flow_temp
|
||||||
|
|
||||||
|
if self.dz_am012_status != self.am012_status
|
||||||
|
tasmota.cmd('_DzSend1 536,' .. self.am012_status) # Send status to Domoticz
|
||||||
|
end
|
||||||
|
self.dz_am012_status = self.am012_status
|
||||||
|
|
||||||
|
if self.dz_am014_substatus != self.am014_substatus
|
||||||
|
tasmota.cmd('_DzSend1 537,' .. self.am014_substatus) # Send substatus to Domoticz
|
||||||
|
end
|
||||||
|
self.dz_am014_substatus = self.am014_substatus
|
||||||
|
end
|
||||||
|
|
||||||
#- display sensor value in the web UI -#
|
#- display sensor value in the web UI -#
|
||||||
def web_sensor()
|
def web_sensor()
|
||||||
if !self.active return nil end # Exit if never decoded something
|
if !self.active return nil end # Exit if never decoded something
|
||||||
import string
|
import string
|
||||||
var msg = string.format("{s}AM012/AM014 State{m}%i/%i{e}"..
|
var msg = string.format("{s}AM012/AM014 State{m}%i/%i{e}"..
|
||||||
"{s}AM024 Relative Power{m}%i %%{e}"..
|
"{s}AM024 Relative Power{m}%i %%{e}"..
|
||||||
@ -138,7 +192,7 @@ load('twai.be')
|
|||||||
* Berry interface
|
* Berry interface
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
bool TWAIBerryDecode(uint32_t identifier, uint32_t data_length_code, uint8_t *data) {
|
bool TWAIBerryDecode(uint32_t bus, uint32_t identifier, uint32_t data_length_code, uint8_t *data) {
|
||||||
bvm *vm = berry.vm;
|
bvm *vm = berry.vm;
|
||||||
if (nullptr == vm) { return false; }
|
if (nullptr == vm) { return false; }
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
@ -149,12 +203,13 @@ bool TWAIBerryDecode(uint32_t identifier, uint32_t data_length_code, uint8_t *da
|
|||||||
uint32_t ident = identifier | data_length_code << 28;
|
uint32_t ident = identifier | data_length_code << 28;
|
||||||
uint32_t data1 = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0];
|
uint32_t data1 = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0];
|
||||||
uint32_t data2 = data[7] << 24 | data[6] << 16 | data[5] << 8 | data[4];
|
uint32_t data2 = data[7] << 24 | data[6] << 16 | data[5] << 8 | data[4];
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: ident %08X, data1 %08X, data2 %08X"), ident, data1, data2);
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d ident %08X, data1 %08X, data2 %08X"), bus +1, ident, data1, data2);
|
||||||
|
|
||||||
|
be_pushint(vm, bus +1); // Bus 1, 2 or 3
|
||||||
be_pushint(vm, ident);
|
be_pushint(vm, ident);
|
||||||
be_pushint(vm, data1);
|
be_pushint(vm, data1);
|
||||||
be_pushint(vm, data2);
|
be_pushint(vm, data2);
|
||||||
int32_t ret = be_pcall(vm, 4); // Number of arguments, self, ident. data1, data2
|
int32_t ret = be_pcall(vm, 5); // Number of arguments, self, bus, ident. data1, data2
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
be_error_pop_all(vm); // Clear Berry stack
|
be_error_pop_all(vm); // Clear Berry stack
|
||||||
}
|
}
|
||||||
@ -167,24 +222,25 @@ bool TWAIBerryDecode(uint32_t identifier, uint32_t data_length_code, uint8_t *da
|
|||||||
|
|
||||||
/*********************************************************************************************/
|
/*********************************************************************************************/
|
||||||
|
|
||||||
bool TWAIBerryConfig(void) {
|
bool TWAIBerryConfig(uint32_t bus) {
|
||||||
bvm *vm = berry.vm;
|
bvm *vm = berry.vm;
|
||||||
if (nullptr == vm) { return false; }
|
if (nullptr == vm) { return false; }
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
if (be_getglobal(vm, "twai")) { // Look for class
|
if (be_getglobal(vm, "twai")) { // Look for class
|
||||||
if (be_getmember(vm, 1, "config")) { // and function
|
if (be_getmember(vm, 1, "config")) { // and function
|
||||||
be_pushvalue(vm, -2);
|
be_pushvalue(vm, -2);
|
||||||
int32_t ret = be_pcall(vm, 1); // Number of arguments, self
|
be_pushint(vm, bus +1); // Bus 1, 2 or 3
|
||||||
|
int32_t ret = be_pcall(vm, 2); // Number of arguments, self, bus
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
be_error_pop_all(vm); // Clear Berry stack
|
be_error_pop_all(vm); // Clear Berry stack
|
||||||
}
|
}
|
||||||
be_pop(vm, 1);
|
be_pop(vm, 2);
|
||||||
if (be_isint(vm, -1)) {
|
if (be_isint(vm, -1)) {
|
||||||
uint32_t config = be_toint(vm, -1);
|
uint32_t config = be_toint(vm, -1);
|
||||||
Twai.speed = config & 0x7; // User input check 0..7
|
Twai.speed[bus] = config & 0x7; // User input check 0..7
|
||||||
uint32_t mode = config >> 3 & 0x3;
|
uint32_t mode = config >> 3 & 0x3;
|
||||||
if (mode > 2) { mode = 2; } // User input check 0..2
|
if (mode > 2) { mode = 2; } // User input check 0..2
|
||||||
Twai.mode = (twai_mode_t)mode;
|
Twai.mode[bus] = (twai_mode_t)mode;
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,12 +255,14 @@ bool TWAIBerryConfig(void) {
|
|||||||
* TWAI low level
|
* TWAI low level
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
bool TWAIStart(void) {
|
bool TWAIStart(int tx, int rx, uint32_t bus = 0);
|
||||||
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)Pin(GPIO_TWAI_TX), (gpio_num_t)Pin(GPIO_TWAI_RX), Twai.mode);
|
bool TWAIStart(int tx, int rx, uint32_t bus) {
|
||||||
|
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)tx, (gpio_num_t)rx, Twai.mode[bus]);
|
||||||
|
g_config.controller_id = bus;
|
||||||
g_config.rx_queue_len = 32;
|
g_config.rx_queue_len = 32;
|
||||||
|
|
||||||
twai_timing_config_t t_config;
|
twai_timing_config_t t_config;
|
||||||
switch (Twai.speed) {
|
switch (Twai.speed[bus]) {
|
||||||
case 0:
|
case 0:
|
||||||
t_config = TWAI_TIMING_CONFIG_25KBITS();
|
t_config = TWAI_TIMING_CONFIG_25KBITS();
|
||||||
break;
|
break;
|
||||||
@ -236,86 +294,88 @@ bool TWAIStart(void) {
|
|||||||
|
|
||||||
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
||||||
|
|
||||||
if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
|
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &Twai.bus[bus]) != ESP_OK) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Not installed"));
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Not installed"), bus +1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (twai_start() != ESP_OK) {
|
if (twai_start_v2(Twai.bus[bus]) != ESP_OK) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Not started"));
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Not started"), bus +1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Reconfigure alerts to detect frame receive, Bus-Off error and RX queue full states
|
// Reconfigure alerts to detect frame receive, Bus-Off error and RX queue full states
|
||||||
uint32_t alerts_to_enable = TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
|
uint32_t alerts_to_enable = TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
|
||||||
if (twai_reconfigure_alerts(alerts_to_enable, NULL) != ESP_OK) {
|
if (twai_reconfigure_alerts_v2(Twai.bus[bus], alerts_to_enable, NULL) != ESP_OK) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Failed to reconfigure CAN alerts"));
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Failed to reconfigure CAN alerts"), bus +1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Twai.installed = true;
|
|
||||||
|
|
||||||
char smode[16];
|
char smode[16];
|
||||||
GetTextIndexed(smode, sizeof(smode), Twai.mode, kTwaiModes);
|
GetTextIndexed(smode, sizeof(smode), Twai.mode[bus], kTwaiModes);
|
||||||
char sspeed[16];
|
char sspeed[16];
|
||||||
GetTextIndexed(sspeed, sizeof(sspeed), Twai.speed, kTwaiSpeeds);
|
GetTextIndexed(sspeed, sizeof(sspeed), Twai.speed[bus], kTwaiSpeeds);
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Started in %s Mode and %sbit/s"), smode, sspeed);
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d using GPIO%02d(Tx) and GPIO%02d(Rx) started in %s Mode and %sbit/s"),
|
||||||
|
bus +1, tx, rx, smode, sspeed);
|
||||||
|
|
||||||
|
Twai.installed[bus] = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************/
|
/*********************************************************************************************/
|
||||||
|
|
||||||
void TWAIStop(void) {
|
void TWAIStop(uint32_t bus) {
|
||||||
if (Twai.installed) {
|
if (Twai.installed[bus]) {
|
||||||
twai_stop();
|
twai_stop_v2(Twai.bus[bus]);
|
||||||
twai_driver_uninstall();
|
twai_driver_uninstall_v2(Twai.bus[bus]);
|
||||||
Twai.installed = false;
|
Twai.installed[bus] = false;
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Stopped"));
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d stopped"), bus +1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************/
|
/*********************************************************************************************/
|
||||||
|
|
||||||
uint32_t TWAICheckAlerts(void) {
|
uint32_t TWAICheckAlerts(uint32_t bus) {
|
||||||
uint32_t alerts_triggered;
|
uint32_t alerts_triggered;
|
||||||
twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(TWAI_POLLING_RATE_MS));
|
twai_read_alerts_v2(Twai.bus[bus], &alerts_triggered, pdMS_TO_TICKS(TWAI_POLLING_RATE_MS));
|
||||||
twai_status_info_t twaistatus;
|
twai_status_info_t twaistatus;
|
||||||
twai_get_status_info(&twaistatus);
|
twai_get_status_info_v2(Twai.bus[bus], &twaistatus);
|
||||||
|
|
||||||
// Handle alerts
|
// Handle alerts
|
||||||
if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
|
if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Alert - Controller has become error passive"));
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Controller has become error passive"), bus +1);
|
||||||
}
|
}
|
||||||
if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
|
if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Alert - A Bit, Stuff, CRC, Form or ACK error has occurred on the bus. Errors %d"), twaistatus.bus_error_count);
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - A Bit, Stuff, CRC, Form or ACK error has occurred on the bus. Errors %d"), bus +1, twaistatus.bus_error_count);
|
||||||
}
|
}
|
||||||
if (alerts_triggered & TWAI_ALERT_RX_QUEUE_FULL) {
|
if (alerts_triggered & TWAI_ALERT_RX_QUEUE_FULL) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Alert - The RX queue is full causing a received frame to be lost. Buffered %d, Missed %d, Overrun %d"),
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - RX queue is full causing a received frame to be lost. Buffered %d, Missed %d, Overrun %d"),
|
||||||
twaistatus.msgs_to_rx, twaistatus.rx_missed_count, twaistatus.rx_overrun_count);
|
bus +1, twaistatus.msgs_to_rx, twaistatus.rx_missed_count, twaistatus.rx_overrun_count);
|
||||||
}
|
}
|
||||||
if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
|
if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Alert - The Transmission failed. Buffered %d, Error %d, Failed %d"),
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Transmission failed. Buffered %d, Error %d, Failed %d"),
|
||||||
twaistatus.msgs_to_tx, twaistatus.tx_error_counter, twaistatus.tx_failed_count);
|
bus +1, twaistatus.msgs_to_tx, twaistatus.tx_error_counter, twaistatus.tx_failed_count);
|
||||||
}
|
|
||||||
if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Alert - The Transmission was successful. Buffered %d"), twaistatus.msgs_to_tx);
|
|
||||||
}
|
}
|
||||||
|
// if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Transmission successful. Buffered %d"), bus +1, twaistatus.msgs_to_tx);
|
||||||
|
// }
|
||||||
|
|
||||||
return alerts_triggered;
|
return alerts_triggered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************/
|
/*********************************************************************************************/
|
||||||
|
|
||||||
void TWAIRead(void) {
|
void TWAIRead(uint32_t bus) {
|
||||||
uint32_t alerts_triggered = TWAICheckAlerts();
|
uint32_t alerts_triggered = TWAICheckAlerts(bus);
|
||||||
// Check if message is received
|
// Check if message is received
|
||||||
if (alerts_triggered & TWAI_ALERT_RX_DATA) {
|
if (alerts_triggered & TWAI_ALERT_RX_DATA) {
|
||||||
// One or more messages received. Handle all.
|
// One or more messages received. Handle all.
|
||||||
twai_message_t message;
|
twai_message_t message;
|
||||||
while (twai_receive(&message, 0) == ESP_OK) {
|
while (twai_receive_v2(Twai.bus[bus], &message, 0) == ESP_OK) {
|
||||||
#ifdef USE_BERRY
|
#ifdef USE_BERRY
|
||||||
if (TWAIBerryDecode(message.identifier, message.data_length_code, message.data)) { continue; }
|
if (TWAIBerryDecode(bus, message.identifier, message.data_length_code, message.data)) { continue; }
|
||||||
#endif // USE_BERRY
|
#endif // USE_BERRY
|
||||||
// Show log if no berry found (Messages come too fast for supporting MQTT at this time)
|
// Show log if no berry found (Messages come too fast for supporting MQTT at this time)
|
||||||
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Id 0x%08X, Rcvd '%*_H'"),
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d Id 0x%08X, Rcvd '%*_H'"),
|
||||||
message.identifier, message.data_length_code, message.data);
|
bus +1, message.identifier, message.data_length_code, message.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,15 +385,88 @@ void TWAIRead(void) {
|
|||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
void TWAIInit(void) {
|
void TWAIInit(void) {
|
||||||
if (!PinUsed(GPIO_TWAI_TX) || !PinUsed(GPIO_TWAI_RX)) { return; }
|
Twai.supported = false;
|
||||||
|
for (uint32_t bus = 0; bus < MAX_TWAI; bus++) {
|
||||||
Twai.speed = TWAI_SPEED_1MBITS;
|
if (PinUsed(GPIO_TWAI_TX, bus) && PinUsed(GPIO_TWAI_RX, bus)) {
|
||||||
Twai.mode = TWAI_MODE_LISTEN_ONLY; // 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
|
Twai.speed[bus] = TWAI_SPEED_1MBITS;
|
||||||
|
Twai.mode[bus] = TWAI_MODE_LISTEN_ONLY; // 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY
|
||||||
#ifdef USE_BERRY
|
#ifdef USE_BERRY
|
||||||
TWAIBerryConfig();
|
TWAIBerryConfig(bus);
|
||||||
#endif // USE_BERRY
|
#endif // USE_BERRY
|
||||||
if (TWAIStart()) {
|
if (TWAIStart(Pin(GPIO_TWAI_TX, bus), Pin(GPIO_TWAI_RX, bus), bus)) {
|
||||||
Twai.supported = true;
|
Twai.supported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Commands
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
const char kTwaiCommands[] PROGMEM = "TWAI|" // Prefix
|
||||||
|
"Send";
|
||||||
|
|
||||||
|
void (* const TwaiCommand[])(void) PROGMEM = {
|
||||||
|
&CmndTWAISend };
|
||||||
|
|
||||||
|
void CmndTWAISend(void) {
|
||||||
|
// TwaiSend<bus> <identifier>[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]
|
||||||
|
// TwaiSend<bus> {"ID":<identifier>,"DATA":[[[[[[[[<data1>],<data2>],<data3>],<data4>],<data5>],<data6>],<data7>],<data8>]]}
|
||||||
|
// An identifier with bit 32 set (0x80000000) is a 29-bit identifier
|
||||||
|
// TwaiSend1 0x481,0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21
|
||||||
|
// TwaiSend1 {"ID":0x481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
|
||||||
|
// TwaiSend2 {"ID":0x80000481,"DATA":[0x03,0x1E,0xFF,0xFF,0xFF,0x01,0x21]}
|
||||||
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_TWAI) && (XdrvMailbox.data_len > 0)) {
|
||||||
|
uint32_t bus = XdrvMailbox.index -1;
|
||||||
|
if (!Twai.installed[bus]) { return; }
|
||||||
|
|
||||||
|
uint32_t data[9] = { 0 }; // Accomodate identifier and 8 data bytes
|
||||||
|
uint32_t data_count = 0;
|
||||||
|
if (XdrvMailbox.data[0] == ('{')) {
|
||||||
|
// parse JSON
|
||||||
|
JsonParser parser(XdrvMailbox.data);
|
||||||
|
JsonParserObject root = parser.getRootObject();
|
||||||
|
if (!root) { return; } // No valid JSON
|
||||||
|
|
||||||
|
JsonParserToken val = root[PSTR("ID")];
|
||||||
|
if (!val) { return; } // We need at least an identifier
|
||||||
|
|
||||||
|
data[0] = val.getUInt();
|
||||||
|
data_count = 1;
|
||||||
|
JsonParserArray arr = root[PSTR("DATA")];
|
||||||
|
for (auto value : arr) { // All data is optional
|
||||||
|
data[data_count] = value.getUInt();
|
||||||
|
data_count++;
|
||||||
|
if (9 == data_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// parse comma separated values
|
||||||
|
data_count = ParseParameters(9, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
twai_message_t message;
|
||||||
|
message.extd = (data[0] & 0x80000000) ? 1 : 0; // Standard (0) vs extended (1) format
|
||||||
|
message.rtr = 0, // Data (0) vs RTR (1) frame
|
||||||
|
message.ss = 0, // Whether the message is single shot (1) (i.e., does not repeat on error)
|
||||||
|
message.self = 0, // Whether the message is a self reception request (1) (loopback)
|
||||||
|
message.dlc_non_comp = 0, // DLC is less than 8 (0)
|
||||||
|
message.identifier = data[0] & 0x1FFFFFFF; // Message ID (11-bits or 29-bits)
|
||||||
|
message.data_length_code = data_count -1; // Payload length (0..8)
|
||||||
|
for (uint8_t i = 0; i < message.data_length_code; i++) {
|
||||||
|
message.data[i] = data[i +1]; // Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
twai_clear_receive_queue_v2(Twai.bus[bus]);
|
||||||
|
//Queue message for transmission
|
||||||
|
if (twai_transmit_v2(Twai.bus[bus], &message, pdMS_TO_TICKS(TWAI_POLLING_RATE_MS)) == ESP_OK) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TWA: Bus%d Message queued for transmission"), bus +1);
|
||||||
|
} else {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TWA: Bus%d Alert - Failed to queue message for transmission"), bus +1);
|
||||||
|
}
|
||||||
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,14 +483,20 @@ bool Xdrv91(uint32_t function) {
|
|||||||
else if (Twai.supported) {
|
else if (Twai.supported) {
|
||||||
switch (function) {
|
switch (function) {
|
||||||
case FUNC_LOOP:
|
case FUNC_LOOP:
|
||||||
if (Twai.installed) {
|
for (uint32_t bus = 0; bus < MAX_TWAI; bus++) {
|
||||||
TWAIRead();
|
if (Twai.installed[bus]) {
|
||||||
|
TWAIRead(bus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case FUNC_COMMAND:
|
||||||
|
result = DecodeCommand(kTwaiCommands, TwaiCommand);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // SOC_TWAI_SUPPORTED
|
||||||
#endif // USE_ESP32_TWAI
|
#endif // USE_ESP32_TWAI
|
||||||
#endif // ESP32
|
#endif // ESP32
|
Loading…
x
Reference in New Issue
Block a user