voltgo

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 6 Imported by: 0

README

Voltgo

A Go library for communicating with Voltgo (and compatible) LiFePO4 batteries via Bluetooth Low Energy (BLE).

These batteries are sold under various brand names including Enerwatt, TCED Worldwide, and others, but all use the same BLE protocol compatible with the Voltgo mobile app.

This library provides a simple interface to connect to and monitor LiFePO4 batteries that use a BMS with Bluetooth support, compatible with the Voltgo mobile app.

Features

  • BLE communication with Voltgo LiFePO4 battery BMS
  • Read battery status (voltage, current, SOC, SOH, temperatures)
  • Read individual cell voltages
  • Read device info (model, capacity, manufacture date)
  • Modbus RTU over BLE GATT protocol, verified against real hardware
  • Cross-platform support (Linux, macOS, Windows)
  • Built on TinyGo Bluetooth library
  • Compatible with Enerwatt, TCED Worldwide, and other branded batteries using the Voltgo protocol

Installation

go get github.com/lumberbarons/voltgo

Requirements

  • Go 1.24 or later
  • Bluetooth Low Energy adapter
  • Platform-specific Bluetooth support:
    • Linux: BlueZ
    • macOS: CoreBluetooth (built-in)
    • Windows: WinRT Bluetooth API

Project Structure

voltgo/
├── battery/            # Battery data structures returned to callers
├── internal/
│   ├── ble/            # BLE connection handling
│   └── protocol/       # Modbus RTU framing and register parsing
├── examples/           # Example applications (scan, basic, monitor)
├── cmd/
│   └── voltgo-cli/     # CLI tool
├── client.go           # Main client interface
├── doc.go
├── Makefile
├── CONTRIBUTING.md     # Contributor guide
├── PROTOCOL.md         # Reverse-engineered protocol documentation
├── go.mod
└── README.md

Quick Start

Scanning for Batteries
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/lumberbarons/voltgo"
)

func main() {
    ctx := context.Background()

    client, err := voltgo.NewClient()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Scan for 10 seconds
    devices, err := client.Scan(ctx, 10*time.Second)
    if err != nil {
        log.Fatal(err)
    }

    for i, device := range devices {
        fmt.Printf("%d. %s (%s) - RSSI: %d dBm\n",
            i+1, device.Name, device.Address, device.RSSI)
    }
}

You should see output like:

1. ZT-25.6V100Ah-1238 (a4:c1:37:43:a4:42) - RSSI: -62 dBm

If nothing shows up:

  • On Linux, make sure your user has permission to use the Bluetooth adapter (typically membership in the bluetooth group) and that bluetoothd is running.
  • A battery that is already connected to the Voltgo phone app stops advertising and won't appear in scans — disconnect the app first.
Reading Battery Status
// Connect to a battery device by its address string
battery, err := client.Connect(ctx, devices[0].Address)
if err != nil {
    log.Fatal(err)
}
defer battery.Disconnect()

// Get current status
status, err := battery.GetStatus(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Voltage: %.2fV\n", status.Voltage)
fmt.Printf("Current: %.2fA\n", status.Current)
fmt.Printf("SOC: %d%%\n", status.SOC)
fmt.Printf("Temperature: %.1f°C\n", status.Temperature)

// Get individual cell voltages
cells, err := battery.GetCellVoltages(ctx)
if err != nil {
    log.Fatal(err)
}

for _, cell := range cells {
    fmt.Printf("Cell %d: %.3fV\n", cell.Index+1, cell.Voltage)
}

Protocol Details

For protocol details, see PROTOCOL.md.

Examples

See the examples/ directory for complete working examples:

  • examples/scan/ - Simple device scanner
  • examples/basic/ - Basic battery communication example
  • examples/monitor/ - Continuous battery monitoring

API Reference

Client
// Create a new client
client, err := voltgo.NewClient()

// Scan for devices
devices, err := client.Scan(ctx, duration)

// Connect to a device by its address string
// (MAC address, or CoreBluetooth UUID on macOS)
battery, err := client.Connect(ctx, devices[0].Address)

// Close the client
client.Close()
Battery
// Check connection status
isConnected := battery.IsConnected()

// Get battery status
status, err := battery.GetStatus(ctx)

// Get cell voltages
cells, err := battery.GetCellVoltages(ctx)

// Get battery info
info, err := battery.GetInfo(ctx)

// Get the parsed status register block, including raw register values
bmsInfo, err := battery.GetBMSInfo(ctx)

// Get the ASCII device-info register block (model, hw version, date)
identity, err := battery.GetDeviceIdentity(ctx)

// Read raw Modbus holding registers
regs, err := battery.ReadRegisters(ctx, startReg, count)

// Disconnect
battery.Disconnect()

Development Status

The protocol (Modbus RTU over BLE GATT) has been reverse-engineered from HCI traces and live probing, and is verified working against real ZT-25.6V100Ah batteries on Linux/BlueZ.

Known gaps (see PROTOCOL.md):

  • Current scaling and charge sign are verified (int16, 0.1A, positive while charging); the discharge sign is still inferred from the int16 encoding
  • Status/protection flag registers are unmapped (all zero on a healthy battery)
  • Write commands (charge/discharge switches, heating) are not yet implemented

Contributing

Contributions welcome! If you have a compatible battery, please test and report results.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

License

MIT

Acknowledgments

  • Protocol analysis based on the Voltgo Android application
  • Built with TinyGo Bluetooth library

Documentation

Overview

Package voltgo provides a Go library for communicating with Voltgo and compatible LiFePO4 batteries via Bluetooth Low Energy (BLE).

These batteries are sold under various brand names including Enerwatt, TCED Worldwide, and others, but use the same BLE protocol compatible with the Voltgo mobile app.

This library implements the BLE protocol used by the Voltgo mobile app to monitor and control LiFePO4 battery management systems (BMS).

Basic usage:

client, err := voltgo.NewClient()
if err != nil {
	log.Fatal(err)
}
defer client.Close()

// Scan for batteries
devices, err := client.Scan(ctx, 10*time.Second)
if err != nil {
	log.Fatal(err)
}

// Connect to a device by its address string
battery, err := client.Connect(ctx, devices[0].Address)
if err != nil {
	log.Fatal(err)
}
defer battery.Disconnect()

// Read battery status
status, err := battery.GetStatus(ctx)
if err != nil {
	log.Fatal(err)
}

Data types returned to callers live in the battery package. BLE transport and Modbus framing are implementation details under internal/.

Protocol Details:

The BLE protocol uses the following UUIDs:

  • Service: 00001006-0000-1000-8000-00805f9b34fb
  • Write: 00001008-0000-1000-8000-00805f9b34fb
  • Notify: 00001007-0000-1000-8000-00805f9b34fb

The batteries speak Modbus RTU framed over GATT: a standard Modbus read-holding-registers request (slave address 0x01, function 0x03, CRC-16/MODBUS) is written to the write characteristic, and the response frame arrives as a notification on the notify characteristic. Frames with an invalid CRC are silently ignored by the BMS. See PROTOCOL.md for the register map.

Index

Constants

View Source
const (
	DefaultTimeout      = 5 * time.Second
	DefaultScanDuration = 10 * time.Second
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Battery

type Battery struct {
	// contains filtered or unexported fields
}

Battery represents a connected battery

func NewBattery added in v0.2.0

func NewBattery(t Transport) *Battery

NewBattery wraps an existing transport in a Battery. Most callers should use Client.Connect instead; this constructor exists for custom transports and testing.

func (*Battery) Disconnect

func (b *Battery) Disconnect() error

Disconnect disconnects from the battery

func (*Battery) GetBMSInfo

func (b *Battery) GetBMSInfo(ctx context.Context) (*battery.BMSInfo, error)

GetBMSInfo reads and parses the status register block. This is the low-level variant of GetStatus and includes the raw registers.

func (*Battery) GetCellVoltages

func (b *Battery) GetCellVoltages(ctx context.Context) ([]battery.Cell, error)

GetCellVoltages retrieves individual cell voltages

func (*Battery) GetDeviceIdentity added in v0.2.0

func (b *Battery) GetDeviceIdentity(ctx context.Context) (*battery.DeviceIdentity, error)

GetDeviceIdentity reads the ASCII device-info register block (model, hardware version, manufacture date).

func (*Battery) GetInfo

func (b *Battery) GetInfo(ctx context.Context) (*battery.Info, error)

GetInfo retrieves battery identity information: chemistry and nominal voltage derived from the cell count, plus the device's ASCII identity strings (model, hardware version, manufacture date).

func (*Battery) GetStatus

func (b *Battery) GetStatus(ctx context.Context) (*battery.Status, error)

GetStatus retrieves the current battery status

func (*Battery) IsConnected

func (b *Battery) IsConnected() bool

IsConnected returns whether the battery is connected

func (*Battery) ReadRegisters

func (b *Battery) ReadRegisters(ctx context.Context, startReg, count uint16) ([]uint16, error)

ReadRegisters reads count holding registers starting at startReg. This is the low-level primitive underlying all queries; the battery silently ignores malformed requests, which surfaces here as a timeout.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the main client for communicating with Voltgo batteries

func NewClient

func NewClient() (*Client, error)

NewClient creates a new Voltgo client

func (*Client) Close

func (c *Client) Close() error

Close closes the client and releases resources

func (*Client) Connect

func (c *Client) Connect(ctx context.Context, address string) (*Battery, error)

Connect connects to a battery device by the address string reported in battery.DeviceInfo: a MAC address ("a4:c1:37:43:a4:42"), or on macOS the CoreBluetooth UUID assigned during scanning.

func (*Client) Scan

func (c *Client) Scan(ctx context.Context, duration time.Duration) ([]battery.DeviceInfo, error)

Scan scans for nearby batteries and returns device info. A device that advertised more than once during the scan window appears once, with the data from its most recent advertisement.

type Transport added in v0.2.0

type Transport interface {
	// Request writes a Modbus RTU frame and returns the response frame.
	Request(ctx context.Context, frame []byte, timeout time.Duration) ([]byte, error)
	Disconnect() error
	IsConnected() bool
}

Transport is the request/response link a Battery uses to reach the BMS. *ble.Connection implements it; tests and alternative transports (e.g. an in-process emulator) can substitute their own.

Directories

Path Synopsis
cmd
voltgo-cli command
examples
basic command
monitor command
scan command
internal
ble
fakebms
Package fakebms is an in-process emulator of a Voltgo battery BMS for testing.
Package fakebms is an in-process emulator of a Voltgo battery BMS for testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL