Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/test_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: OpenDTU-onBattery Test Build
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this new workflow? based on the commit it looks like it was added by accident.


on: workflow_dispatch

jobs:
get_default_envs:
name: Gather Environments
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/setup-python@v4
with:
python-version: "3.9"

- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Get default environments
id: envs
run: |
echo "environments=$(pio project config --json-output | jq -cr '.[1][1][0][1]|split(",")')" >> $GITHUB_OUTPUT
outputs:
environments: ${{ steps.envs.outputs.environments }}

build:
name: Build Enviornments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
matrix:
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
steps:
- uses: actions/checkout@v3

- name: Get tags
run: git fetch --force --tags origin

- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v3
with:
path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"

- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Setup Node.js and yarn
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "yarn"
cache-dependency-path: "webapp/yarn.lock"

- name: Install WebApp dependencies
run: yarn --cwd webapp install --frozen-lockfile

- name: Build WebApp
run: yarn --cwd webapp build

- name: Build firmware
run: pio run -e ${{ matrix.environment }}

- name: Rename Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin

- name: Rename Factory Firmware
run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin

- uses: actions/upload-artifact@v3
with:
name: opendtu-onbattery-${{ matrix.environment }}
path: |
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.bin
.pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ designs](https://opendtu-onbattery.net/3rd_party/cases/) available for you to
print yourself.

### Ready-To-Use

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert the changes to the README. By looking at the commits it looks like those changes got introduced by accident.

If you are interested in ready-to-use hardware available for sale, the
OpenDTU-OnBattery project endorses the **[OpenDTU Fusion
board](https://opendtu-onbattery.net/3rd_party/opendtu_fusion/)**.
Expand Down Expand Up @@ -125,8 +124,6 @@ To find out what's new or improved have a look at the
[releases](https://github.com/hoylabs/OpenDTU-OnBattery/releases).

## Project State

OpenDTU-OnBattery is actively maintained. Please note that OpenDTU-OnBattery
may change significantly during its development. Bug reports, comments, feature
requests and pull requests are welcome!

Expand Down
9 changes: 9 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,19 @@ struct BATTERY_SERIAL_CONFIG_T {
};
using BatterySerialConfig = struct BATTERY_SERIAL_CONFIG_T;

struct BATTERY_JKBMSCAN_CONFIG_T {
uint8_t NumberOfCells;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we know the number of cells based on the values that we retrieve from the BMS?

uint8_t CanProtocolVersion;
};
using BatteryJkBmsCanConfig = struct BATTERY_JKBMSCAN_CONFIG_T;

struct BATTERY_CONFIG_T {
bool Enabled;
uint8_t Provider;
BatteryMqttConfig Mqtt;
BatteryZendureConfig Zendure;
BatterySerialConfig Serial;
BatteryJkBmsCanConfig JkBmsCan;
bool EnableDischargeCurrentLimit;
float DischargeCurrentLimit;
float DischargeCurrentLimitBelowSoc;
Expand Down Expand Up @@ -486,6 +493,7 @@ class ConfigurationClass {
static void serializeBatteryZendureConfig(BatteryZendureConfig const& source, JsonObject& target);
static void serializeBatteryMqttConfig(BatteryMqttConfig const& source, JsonObject& target);
static void serializeBatterySerialConfig(BatterySerialConfig const& source, JsonObject& target);
static void serializeBatteryJkBmsCanConfig(BatteryJkBmsCanConfig const& source, JsonObject& target);
static void serializePowerLimiterConfig(PowerLimiterConfig const& source, JsonObject& target);
static void serializeGridChargerConfig(GridChargerConfig const& source, JsonObject& target);
static void serializeGridChargerCanConfig(GridChargerCanConfig const& source, JsonObject& target);
Expand All @@ -503,6 +511,7 @@ class ConfigurationClass {
static void deserializeBatteryZendureConfig(JsonObject const& source, BatteryZendureConfig& target);
static void deserializeBatteryMqttConfig(JsonObject const& source, BatteryMqttConfig& target);
static void deserializeBatterySerialConfig(JsonObject const& source, BatterySerialConfig& target);
static void deserializeBatteryJkBmsCanConfig(JsonObject const& source, BatteryJkBmsCanConfig& target);
static void deserializePowerLimiterConfig(JsonObject const& source, PowerLimiterConfig& target);
static void deserializeGridChargerConfig(JsonObject const& source, GridChargerConfig& target);
static void deserializeGridChargerCanConfig(JsonObject const& source, GridChargerCanConfig& target);
Expand Down
1 change: 1 addition & 0 deletions include/PinMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ struct PinMapping_t {
gpio_num_t battery_rxen;
gpio_num_t battery_tx;
gpio_num_t battery_txen;
uint8_t battery_can_type;
gpio_num_t huawei_miso;
gpio_num_t huawei_mosi;
gpio_num_t huawei_clk;
Expand Down
2 changes: 2 additions & 0 deletions include/battery/CanReceiver.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

Expand All @@ -18,6 +19,7 @@ class CanReceiver : public Provider {
protected:
uint8_t readUnsignedInt8(uint8_t *data);
uint16_t readUnsignedInt16(uint8_t *data);
uint16_t readBigEndianUnsignedInt16(uint8_t *data);
int16_t readSignedInt16(uint8_t *data);
uint32_t readUnsignedInt32(uint8_t *data);
int32_t readSignedInt24(uint8_t *data);
Expand Down
16 changes: 16 additions & 0 deletions include/battery/jkbmscan/HassIntegration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <battery/HassIntegration.h>
#include <battery/jkbmscan/Stats.h>

namespace Batteries::JkBmsCan {

class HassIntegration : public ::Batteries::HassIntegration {
public:
explicit HassIntegration(std::shared_ptr<Stats> spStats);

void publishSensors() const final;
};

} // namespace Batteries::JkBmsCan
28 changes: 28 additions & 0 deletions include/battery/jkbmscan/Provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <memory>
#include <driver/twai.h>
#include <battery/CanReceiver.h>
#include <battery/jkbmscan/Stats.h>
#include <battery/jkbmscan/HassIntegration.h>

namespace Batteries::JkBmsCan {

class Provider : public ::Batteries::CanReceiver {
public:
Provider();
bool init() final;
void onMessage(twai_message_t rx_message) final;

std::shared_ptr<::Batteries::Stats> getStats() const final { return _stats; }
std::shared_ptr<::Batteries::HassIntegration> getHassIntegration() final { return _hassIntegration; }

private:
void dummyData();

std::shared_ptr<Stats> _stats;
std::shared_ptr<HassIntegration> _hassIntegration;
};

} // namespace Batteries::JkBmsCan
72 changes: 72 additions & 0 deletions include/battery/jkbmscan/Stats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <battery/Stats.h>

namespace Batteries::JkBmsCan {

class Stats : public ::Batteries::Stats {
friend class Provider;

public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
// bool getImmediateChargingRequest() const { return _chargeImmediately; } ;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supported? If not, please remove it

float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ;

private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }

float _chargeVoltage;
float _chargeCurrentLimitation;
float _dischargeVoltageLimitation;
uint8_t _stateOfHealth;
float _temperature;

float _cellVoltage[25];
float _packVoltage;
float _MaxCellVoltage;
uint8_t _MaxCellVoltageNumber;
float _MinCellVoltage;
uint8_t _MinCellVoltageNumber;

float _capacityRemaining;
float _fullChargeCapacity;
float _cycleCapacity;
uint16_t _cycleCount;

uint32_t _bmsRunTime;
uint16_t _heaterCurrent;


bool _alarmOverCurrentDischarge;
bool _alarmOverCurrentCharge;
bool _alarmUnderTemperature;
bool _alarmOverTemperature;
bool _alarmUnderVoltage;
bool _alarmOverVoltage;
bool _alarmBmsInternal;

bool _warningHighCurrentDischarge;
bool _warningHighCurrentCharge;
bool _warningLowTemperature;
bool _warningHighTemperature;
bool _warningLowVoltage;
bool _warningHighVoltage;
bool _warningBmsInternal;

bool _chargeEnabled;
bool _dischargeEnabled;
bool _balanceEnabled;
bool _heaterEnabled;
bool _accEnabled;
bool _chargerPluged;

bool _chargeRequest;
bool _chargeAndHeat;

uint8_t _moduleCount;
uint8_t _JkBmsCanVersion;
Copy link

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The private member _JkBmsCanVersion is declared but never used or initialized. Consider removing it or implementing its assignment and usage.

Suggested change
uint8_t _JkBmsCanVersion;

Copilot uses AI. Check for mistakes.

};

} // namespace Batteries::JkBmsCan
2 changes: 2 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@
#define BATTERY_PROVIDER 0 // Pylontech CAN receiver
#define BATTERY_SERIAL_INTERFACE 0
#define BATTERY_SERIAL_POLLING_INTERVAL 5
#define BATTERY_NUMBER_OF_CELLS 16 // Default number of cells for a battery Pack
#define BATTERY_JKBMS_CAN_PROTOCOL_VERSION 1 // Default CAN protocol version for the JK BMS
#define BATTERY_ENABLE_DISCHARGE_CURRENT_LIMIT false
#define BATTERY_DISCHARGE_CURRENT_LIMIT 0.0
#define BATTERY_DISCHARGE_CURRENT_LIMIT_BELOW_SOC 100.0
Expand Down
4 changes: 2 additions & 2 deletions platformio_override.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
; Under Linux, the ports are in the format /dev/tty***, typically /dev/ttyUSB0
; To look up the port, execute ls /dev/tty*, then connect the device
; then execute ls /dev/tty* again and check which device was added
;monitor_port = COM4
;upload_port = COM4
monitor_port = /dev/ttyACM2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert the changes in this file

upload_port = /dev/ttyACM2


; you can define your personal board and/or settings here
Expand Down
16 changes: 16 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ void ConfigurationClass::serializeBatterySerialConfig(BatterySerialConfig const&
target["polling_interval"] = source.PollingInterval;
}

void ConfigurationClass::serializeBatteryJkBmsCanConfig(BatteryJkBmsCanConfig const& source, JsonObject& target)
{
target["number_of_cells"] = source.NumberOfCells;
target["can_protocol_version"] = source.CanProtocolVersion;
}

void ConfigurationClass::serializePowerLimiterConfig(PowerLimiterConfig const& source, JsonObject& target)
{
char serialBuffer[sizeof(uint64_t) * 8 + 1];
Expand Down Expand Up @@ -436,6 +442,9 @@ bool ConfigurationClass::write()

JsonObject battery_serial = battery["serial"].to<JsonObject>();
serializeBatterySerialConfig(config.Battery.Serial, battery_serial);

JsonObject battery_jkbmscan = battery["jkbmscan"].to<JsonObject>();
serializeBatteryJkBmsCanConfig(config.Battery.JkBmsCan, battery_jkbmscan);

JsonObject gridcharger = doc["gridcharger"].to<JsonObject>();
serializeGridChargerConfig(config.GridCharger, gridcharger);
Expand Down Expand Up @@ -603,6 +612,12 @@ void ConfigurationClass::deserializeBatterySerialConfig(JsonObject const& source
target.PollingInterval = source["polling_interval"] | BATTERY_SERIAL_POLLING_INTERVAL;
}

void ConfigurationClass::deserializeBatteryJkBmsCanConfig(JsonObject const& source, BatteryJkBmsCanConfig& target)
{
target.NumberOfCells = source["number_of_cells"] | BATTERY_NUMBER_OF_CELLS;
target.CanProtocolVersion = source["can_protocol_version"] | BATTERY_JKBMS_CAN_PROTOCOL_VERSION;
}

void ConfigurationClass::deserializePowerLimiterConfig(JsonObject const& source, PowerLimiterConfig& target)
{
auto serialBin = [](String const& input) -> uint64_t {
Expand Down Expand Up @@ -884,6 +899,7 @@ bool ConfigurationClass::read()
deserializeBatteryZendureConfig(battery["zendure"], config.Battery.Zendure);
deserializeBatteryMqttConfig(battery["mqtt"], config.Battery.Mqtt);
deserializeBatterySerialConfig(battery["serial"], config.Battery.Serial);
deserializeBatteryJkBmsCanConfig(battery["jkbmscan"], config.Battery.JkBmsCan);

JsonObject gridcharger = doc["gridcharger"];
deserializeGridChargerConfig(gridcharger, config.GridCharger);
Expand Down
6 changes: 6 additions & 0 deletions src/PinMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ static const char* TAG = "pinmapping";
#define BATTERY_PIN_RX GPIO_NUM_NC
#endif

#ifndef BATTERY_CAN_TYPE
#define BATTERY_CAN_TYPE 1
#endif

#ifdef PYLONTECH_PIN_RX
#undef BATTERY_PIN_RX
#define BATTERY_PIN_RX PYLONTECH_PIN_RX
Expand Down Expand Up @@ -305,6 +309,7 @@ PinMappingClass::PinMappingClass()
_pinMapping.battery_rxen = BATTERY_PIN_RXEN;
_pinMapping.battery_tx = BATTERY_PIN_TX;
_pinMapping.battery_txen = BATTERY_PIN_TXEN;
_pinMapping.battery_can_type = BATTERY_CAN_TYPE;

_pinMapping.huawei_miso = HUAWEI_PIN_MISO;
_pinMapping.huawei_mosi = HUAWEI_PIN_MOSI;
Expand Down Expand Up @@ -415,6 +420,7 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.battery_rxen = doc[i]["battery"]["rxen"] | BATTERY_PIN_RXEN;
_pinMapping.battery_tx = doc[i]["battery"]["tx"] | BATTERY_PIN_TX;
_pinMapping.battery_txen = doc[i]["battery"]["txen"] | BATTERY_PIN_TXEN;
_pinMapping.battery_can_type = doc[i]["battery"]["can_type"] | BATTERY_CAN_TYPE;

_pinMapping.huawei_miso = doc[i]["huawei"]["miso"] | HUAWEI_PIN_MISO;
_pinMapping.huawei_mosi = doc[i]["huawei"]["mosi"] | HUAWEI_PIN_MOSI;
Expand Down
Loading