2026-02-14 08:53:17 +00:00
package tools
import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"runtime"
)
// I2CTool provides I2C bus interaction for reading sensors and controlling peripherals.
type I2CTool struct { }
func NewI2CTool ( ) * I2CTool {
return & I2CTool { }
}
func ( t * I2CTool ) Name ( ) string {
return "i2c"
}
func ( t * I2CTool ) Description ( ) string {
return "Interact with I2C bus devices for reading sensors and controlling peripherals. Actions: detect (list buses), scan (find devices on a bus), read (read bytes from device), write (send bytes to device). Linux only."
}
2026-02-18 19:48:23 +00:00
func ( t * I2CTool ) Parameters ( ) map [ string ] any {
return map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "object" ,
2026-02-18 19:48:23 +00:00
"properties" : map [ string ] any {
"action" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "string" ,
"enum" : [ ] string { "detect" , "scan" , "read" , "write" } ,
"description" : "Action to perform: detect (list available I2C buses), scan (find devices on a bus), read (read bytes from a device), write (send bytes to a device)" ,
} ,
2026-02-18 19:48:23 +00:00
"bus" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "string" ,
"description" : "I2C bus number (e.g. \"1\" for /dev/i2c-1). Required for scan/read/write." ,
} ,
2026-02-18 19:48:23 +00:00
"address" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "integer" ,
"description" : "7-bit I2C device address (0x03-0x77). Required for read/write." ,
} ,
2026-02-18 19:48:23 +00:00
"register" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "integer" ,
"description" : "Register address to read from or write to. If set, sends register byte before read/write." ,
} ,
2026-02-18 19:48:23 +00:00
"data" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "array" ,
2026-02-18 19:48:23 +00:00
"items" : map [ string ] any { "type" : "integer" } ,
2026-02-14 08:53:17 +00:00
"description" : "Bytes to write (0-255 each). Required for write action." ,
} ,
2026-02-18 19:48:23 +00:00
"length" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "integer" ,
"description" : "Number of bytes to read (1-256). Default: 1. Used with read action." ,
} ,
2026-02-18 19:48:23 +00:00
"confirm" : map [ string ] any {
2026-02-14 08:53:17 +00:00
"type" : "boolean" ,
"description" : "Must be true for write operations. Safety guard to prevent accidental writes." ,
} ,
} ,
"required" : [ ] string { "action" } ,
}
}
2026-02-18 19:48:23 +00:00
func ( t * I2CTool ) Execute ( ctx context . Context , args map [ string ] any ) * ToolResult {
2026-02-14 08:53:17 +00:00
if runtime . GOOS != "linux" {
return ErrorResult ( "I2C is only supported on Linux. This tool requires /dev/i2c-* device files." )
}
action , ok := args [ "action" ] . ( string )
if ! ok {
return ErrorResult ( "action is required" )
}
switch action {
case "detect" :
return t . detect ( )
case "scan" :
return t . scan ( args )
case "read" :
return t . readDevice ( args )
case "write" :
return t . writeDevice ( args )
default :
return ErrorResult ( fmt . Sprintf ( "unknown action: %s (valid: detect, scan, read, write)" , action ) )
}
}
// detect lists available I2C buses by globbing /dev/i2c-*
func ( t * I2CTool ) detect ( ) * ToolResult {
matches , err := filepath . Glob ( "/dev/i2c-*" )
if err != nil {
return ErrorResult ( fmt . Sprintf ( "failed to scan for I2C buses: %v" , err ) )
}
if len ( matches ) == 0 {
2026-02-18 19:48:23 +00:00
return SilentResult (
"No I2C buses found. You may need to:\n1. Load the i2c-dev module: modprobe i2c-dev\n2. Check that I2C is enabled in device tree\n3. Configure pinmux for your board (see hardware skill)" ,
)
2026-02-14 08:53:17 +00:00
}
type busInfo struct {
Path string ` json:"path" `
Bus string ` json:"bus" `
}
buses := make ( [ ] busInfo , 0 , len ( matches ) )
re := regexp . MustCompile ( ` /dev/i2c-(\d+) ` )
for _ , m := range matches {
if sub := re . FindStringSubmatch ( m ) ; sub != nil {
buses = append ( buses , busInfo { Path : m , Bus : sub [ 1 ] } )
}
}
result , _ := json . MarshalIndent ( buses , "" , " " )
return SilentResult ( fmt . Sprintf ( "Found %d I2C bus(es):\n%s" , len ( buses ) , string ( result ) ) )
}
2026-02-24 13:52:25 +00:00
// Helper functions for I2C operations (used by platform-specific implementations)
2026-02-14 08:53:17 +00:00
// isValidBusID checks that a bus identifier is a simple number (prevents path injection)
2026-02-24 13:52:25 +00:00
//
//nolint:unused // Used by i2c_linux.go
2026-02-14 08:53:17 +00:00
func isValidBusID ( id string ) bool {
matched , _ := regexp . MatchString ( ` ^\d+$ ` , id )
return matched
}
// parseI2CAddress extracts and validates an I2C address from args
2026-02-24 13:52:25 +00:00
//
//nolint:unused // Used by i2c_linux.go
2026-02-18 19:48:23 +00:00
func parseI2CAddress ( args map [ string ] any ) ( int , * ToolResult ) {
2026-02-14 08:53:17 +00:00
addrFloat , ok := args [ "address" ] . ( float64 )
if ! ok {
return 0 , ErrorResult ( "address is required (e.g. 0x38 for AHT20)" )
}
addr := int ( addrFloat )
if addr < 0x03 || addr > 0x77 {
return 0 , ErrorResult ( "address must be in valid 7-bit range (0x03-0x77)" )
}
return addr , nil
}
// parseI2CBus extracts and validates an I2C bus from args
2026-02-24 13:52:25 +00:00
//
//nolint:unused // Used by i2c_linux.go
2026-02-18 19:48:23 +00:00
func parseI2CBus ( args map [ string ] any ) ( string , * ToolResult ) {
2026-02-14 08:53:17 +00:00
bus , ok := args [ "bus" ] . ( string )
if ! ok || bus == "" {
return "" , ErrorResult ( "bus is required (e.g. \"1\" for /dev/i2c-1)" )
}
if ! isValidBusID ( bus ) {
return "" , ErrorResult ( "invalid bus identifier: must be a number (e.g. \"1\")" )
}
return bus , nil
}