Skip to content
Draft
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# CHANGELOG

## Version 2.3.1, 2026-07-20, `kezarjg`

- Fix: correct the inverted battery-power sign on smart 453/forfour (`SQ`) and smart
ED/fortwo (`SE`). Both OVMS smart modules report `v.b.power` with the wrong sign —
negative while driving (consuming), positive while charging — the opposite of the
OVMS core / Iternio convention. ABRP uses `power` for consumption calibration, so it
read consumption as regen and the calibrated reference consumption drifted toward zero
(issue #40). `overrideMetricMap` now corrects `power` for these vehicles by keeping the
module's power magnitude and taking its sign from `v.b.current`, which the same modules
report correctly. This is self-healing: `v.b.current` is correct in both the current
(buggy) and a future upstream-fixed firmware, so once the module's `v.b.power` sign is
fixed the correction becomes a no-op — no plugin change needed to retire it. Only
`power` is affected; `current`, `is_charging`, and `is_dcfc` are unchanged. Upstream
firmware bugs filed separately against `vehicle_smarteq` and `vehicle_smarted`.

## Version 2.3.0, 2026-06-25, `kezarjg`

- Switched telemetry to batched bulk uploads (`/1/tlm/bulk`) behind a queue, with
Expand Down
21 changes: 20 additions & 1 deletion lib/abrp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// Module constants
const OVMS_API_KEY = '32b2162f-9599-4647-8139-66e9f9528370'
const VERSION = '2.3.0'
const VERSION = '2.3.1'
const Logger = logger()

// Configuration constants
Expand Down Expand Up @@ -311,6 +311,25 @@ function overrideMetricMap() {
};
}
break;
case 'SQ':
case 'SE':
// smart 453/forfour (SQ) and smart ED/fortwo (SE) report v.b.power with an
// inverted sign (negative while driving/consuming) until the upstream firmware
// fix lands (see iternio/ovms-link#40). v.b.current is correctly signed
// (discharge-positive) in BOTH the buggy and the fixed firmware, so keep power's
// magnitude and take its sign from current: corrects the bug now, and becomes a
// no-op once v.b.power's sign is fixed upstream (no plugin change needed).
if (entry.key === 'power') {
entry.requiredMetrics = ['v.b.power', 'v.b.current'];
entry.metric = function(metrics) {
var power = metrics['v.b.power'];
var current = metrics['v.b.current'];
if (current < 0) { return -Math.abs(power); }
if (current > 0) { return Math.abs(power); }
return power; // current == 0: power is ~0, sign irrelevant
};
}
break;
// Add cases for other vehicle types as needed
}
});
Expand Down
62 changes: 62 additions & 0 deletions lib/abrp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,65 @@ describe('charge/drive overlap state machine', () => {
expect(count('ticker.1')).toBe(1) // sampler subscribed at startup
})
})

// smart 453/forfour (SQ) and smart ED/fortwo (SE) report v.b.power with an inverted
// sign (charge-positive) until the upstream firmware fix; v.b.current is correctly
// signed in both firmware states. overrideMetricMap corrects power by keeping its
// magnitude and taking its sign from current: fixes the bug now, no-op once fixed
// upstream. See iternio/ovms-link#40.
describe('overrideMetricMap: smart SQ/SE power sign correction', () => {
function withSmart(present, vtype) {
const abrp = loadAbrp({
OvmsMetrics: {
HasValue: (k) => Object.prototype.hasOwnProperty.call(present, k),
GetValues: (keys) => {
const o = {}
keys.forEach((k) => {
o[k] = present[k]
})
return o
},
Value: (k) => (k === 'v.type' ? vtype : present[k]),
},
})
abrp.__test.overrideMetricMap()
return abrp
}

test('SQ: inverted power while driving is corrected to discharge-positive', () => {
// driving: current +9A (correct, discharge), power reported -3.0kW (inverted)
const abrp = withSmart({ 'v.b.power': -3.0, 'v.b.current': 9 }, 'SQ')
expect(abrp.getOVMSMetric('power')).toEqual([true, 3.0])
})

test('SQ: inverted power while charging is corrected to charge-negative', () => {
// charging: current -20A (correct, charge), power reported +7.0kW (inverted)
const abrp = withSmart({ 'v.b.power': 7.0, 'v.b.current': -20 }, 'SQ')
expect(abrp.getOVMSMetric('power')).toEqual([true, -7.0])
})

test('SQ: already-correct power (post upstream fix) passes through unchanged', () => {
const abrp = withSmart({ 'v.b.power': 3.0, 'v.b.current': 9 }, 'SQ')
expect(abrp.getOVMSMetric('power')).toEqual([true, 3.0])
})

test('SQ: zero current leaves power as-is (magnitude ~0, sign irrelevant)', () => {
const abrp = withSmart({ 'v.b.power': -0.02, 'v.b.current': 0 }, 'SQ')
expect(abrp.getOVMSMetric('power')).toEqual([true, -0.02])
})

test('SE shares the smart override: inverted power corrected', () => {
const abrp = withSmart({ 'v.b.power': -3.0, 'v.b.current': 9 }, 'SE')
expect(abrp.getOVMSMetric('power')).toEqual([true, 3.0])
})

test('SQ: power is unsupported when v.b.current is absent', () => {
const abrp = withSmart({ 'v.b.power': -3.0 }, 'SQ')
expect(abrp.getOVMSMetric('power')).toEqual([false, null])
})

test('non-smart vehicle: power is a plain passthrough (no sign correction)', () => {
const abrp = withSmart({ 'v.b.power': -3.0, 'v.b.current': 9 }, 'UNKNOWN')
expect(abrp.getOVMSMetric('power')).toEqual([true, -3.0])
})
})