Compare commits

...

5 Commits

Author SHA1 Message Date
Terekhin Alexandr 0bd594d32d Merge pull request 'EVSE to EV, refactor' (#1) from feature/refactor into master 3 years ago
didinst 9e504185ee
Split and move code, refactor, multiarch build directives 3 years ago
didinst 8642581449
109 full impl 3 years ago
didinst 213f5b5bd5
Merge evse to ev communication 3 years ago
didinst f0ea05fa96
Add CAN implementation 3 years ago
  1. 151
      can.go
  2. 15
      can_win.go
  3. 45
      candump.go
  4. 233
      chademo.go
  5. 71
      files.go
  6. 6
      files_test.go
  7. 2
      go.mod
  8. 175
      types.go

151
can.go

@ -1,48 +1,133 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"fmt"
"regexp"
"strconv"
"encoding/binary"
"errors"
"io"
"net"
"time"
"unsafe"
"golang.org/x/sys/unix"
)
type CanFrame struct {
canid uint16
payload []uint8
date string
// NewCan New returns a new CAN bus socket.
func NewCan() (*Socket, error) {
fd, err := unix.Socket(unix.AF_CAN, unix.SOCK_RAW, unix.CAN_RAW)
if err != nil {
return nil, err
}
return &Socket{dev: device{fd}}, nil
}
// (2022-07-08 16:54:15.587099) can0 100 [8] 00 00 00 00 00 00 64 00
const expr = "\\((.*)\\)\\s+can\\d+\\s+(\\d+)\\s+\\[\\d\\]\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)"
const length = 11
// Socket is a high-level representation of a CANBus socket.
type Socket struct {
iface *net.Interface
addr *unix.SockaddrCAN
dev device
}
var pattern = regexp.MustCompile(expr)
// Close closes the CAN bus socket.
func (sck *Socket) Close() error {
return unix.Close(sck.dev.fd)
}
func FromString(text *string) *CanFrame {
submatch := pattern.FindStringSubmatch(*text)
if len(submatch) == length {
canid, err := strconv.Atoi(submatch[2])
if err != nil {
return nil
}
func (sck *Socket) Bind(addr string, filter []unix.CanFilter) error {
iface, err := net.InterfaceByName(addr)
if err != nil {
return err
}
payload := make([]uint8, 8)
for idx, octet := range submatch[3:] {
val, err := strconv.ParseUint(octet, 16, 0)
if err != nil {
fmt.Println(err)
continue
}
sck.iface = iface
sck.addr = &unix.SockaddrCAN{Ifindex: sck.iface.Index}
payload[idx] = uint8(val)
}
//add filter
if filter != nil {
unix.SetsockoptCanRawFilter(sck.dev.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, filter)
}
return &CanFrame{
date: submatch[1],
canid: uint16(canid),
payload: payload,
}
return unix.Bind(sck.dev.fd, sck.addr)
}
// Recv receives data from the CAN socket.
// id is the CAN_frame id the data was originated from.
func (sck *Socket) Recv() (id uint32, data []byte, err error) {
var buf [frameSize]byte
n, err := io.ReadFull(sck.dev, buf[:])
if err != nil {
return id, data, err
}
if n != len(buf) {
return id, data, io.ErrUnexpectedEOF
}
id = binary.LittleEndian.Uint32(buf[:4])
id &= unix.CAN_SFF_MASK
data = make([]byte, buf[4])
copy(data, buf[8:])
return id, data, nil
}
type device struct {
fd int
}
func (d device) Read(data []byte) (int, error) {
return unix.Read(d.fd, data)
}
func (d device) Write(data []byte) (int, error) {
return unix.Write(d.fd, data)
}
const frameSize = unsafe.Sizeof(canframe{})
// frame is a can_frame.
type canframe struct {
ID uint32
Len byte
_ [3]byte
Data [8]byte
}
func StartCan(canbus string) (<-chan *CanFrame, error) {
var filter []unix.CanFilter = []unix.CanFilter{
{Id: CAN_ID_100, Mask: 0xFFF},
{Id: CAN_ID_101, Mask: 0xFFF},
{Id: CAN_ID_102, Mask: 0xFFF},
{Id: CAN_ID_108, Mask: 0xFFF},
{Id: CAN_ID_109, Mask: 0xFFF},
}
return nil
if socket, err := NewCan(); err == nil {
if cerr := socket.Bind(canbus, filter); cerr == nil {
canevents := make(chan *CanFrame)
go func() {
defer close(canevents)
defer socket.Close()
for { //TODO implement stop
if id, data, serr := socket.Recv(); serr == nil {
canevents <- &CanFrame{
date: time.Now().String(),
canid: id,
payload: data,
}
}
}
}()
return canevents, nil
}
}
return nil, errors.New("can't bind can socket")
}

@ -0,0 +1,15 @@
//go:build windows
// +build windows
package main
func StartCan(canbus string) (<-chan *CanFrame, error) {
return nil, &NotImplError{}
}
type NotImplError struct {
}
func (e *NotImplError) Error() string {
return "Not implemented for windows platform"
}

@ -1,7 +1,6 @@
package main
import (
"bufio"
"flag"
"fmt"
"os"
@ -10,6 +9,8 @@ import (
func main() {
filename := flag.String("f", "", "Candump filename")
canbus := flag.String("c", "", "CAN bus interface")
all := flag.Bool("a", false, "Show all packets")
flag.Parse()
var frames <-chan *CanFrame
@ -17,8 +18,17 @@ func main() {
switch {
case len(*filename) > 0:
fmt.Printf("Open %v\n", *filename)
frames = readfile(filename)
fmt.Printf("Open file %v\n", *filename)
frames = Readfile(filename)
case len(*canbus) > 0:
fmt.Printf("Open device %v\n", canbus)
var err error
frames, err = StartCan(*canbus)
if err != nil {
fmt.Println(err)
return
}
default:
flag.Usage()
}
@ -37,7 +47,7 @@ func main() {
lastValue := last[eventType]
value, date := event.GetValue()
if lastValue != value {
if lastValue != value || *all {
fmt.Printf("%v | %s = %v\n", *date, eventType, value)
last[eventType] = value
}
@ -47,30 +57,3 @@ func main() {
//}
}
}
func readfile(filename *string) <-chan *CanFrame {
c := make(chan *CanFrame)
go func() {
defer close(c)
file, err := os.Open(*filename)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := scanner.Text()
c <- FromString(&text)
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
}
}()
return c
}

@ -4,37 +4,6 @@ import (
"fmt"
)
const (
CAN_ID_100 = 100
CAN_ID_101 = 101
CAN_ID_102 = 102
DISABLED VehicleChargingEnabled = iota
ENABLED
PARKING ShiftLeverPosition = iota
OTHER
NORMAL SystemFault = iota
FAULT
CONTACTOR_CLOSED ContactorVehicleStatus = iota
CONTACTOR_OPEN
NO_REQUEST StopRequest = iota
STOP_REQUEST
)
type VehicleChargingEnabled uint8
type ShiftLeverPosition uint8
type SystemFault uint8
type ContactorVehicleStatus uint8
type StopRequest uint8
type ChademoEvent interface {
// GetValue Return processed/calculated value and timestamp string
GetValue() (interface{}, *string)
@ -62,8 +31,8 @@ type MaxChargingTimeM frame
// EstChargingTimeM Estimated remaining time before the end of charging calculated by EV
type EstChargingTimeM frame
// ControlProtocolNumber Software version of control protocol to which EV corresponds
type ControlProtocolNumber frame
// EVControlProtocolNumber Software version of control protocol to which EV corresponds
type EVControlProtocolNumber frame
// TargetBatteryVoltage Target battery voltage ed charging voltage at the vehicle inlet terminals
type TargetBatteryVoltage frame
@ -104,6 +73,53 @@ type HighBattTemperature frame
// BattVoltageDeviationErr Status flag indicating whether or not the vehicle battery voltage deviates from the output voltage measured by the station
type BattVoltageDeviationErr frame
// EVSE entity from here
// EVcontWeldingDetectSupport Identifier indicating whether or not the station deals with EV contactor welding detection
type EVcontWeldingDetectSupport frame
// AvailableOutputVoltage Maximum output voltage value at the vehicle connector terminals
type AvailableOutputVoltage frame
// AvailableOutputCurrent Maximum output current value of the station
type AvailableOutputCurrent frame
// ThresholdVoltage Threshold voltage to stop the charging process in order to protect vehicle battery
type ThresholdVoltage frame
// EVSEControlProtocolNumber Software version number of control protocol or charging sequences that the station deals with
type EVSEControlProtocolNumber frame
// OutputVoltage Supply voltage value of the output circuit in the station
type OutputVoltage frame
// OutputCurrent Supply current value of the output circuit in the station
type OutputCurrent frame
// RemainingChargingTimeS Remaining time before the end of charging (counted by 10 s)
type RemainingChargingTimeS frame
// RemainingChargingTimeM Remaining time before the end of charging (counted by min)
type RemainingChargingTimeM frame
// StationStatus Status flag indicating the energy transfer from the station
type StationStatus frame
// StationMalfunction Status flag indicating whether or not there is a malfunction caused by the station
type StationMalfunction frame
// VehicleConnectorLock Status flag indicating Vehicle connector lock the electromagnetic lock status of vehicle connector
type VehicleConnectorLock frame
// BatteryIncompatibility Status flag indicating the compatibility of vehicle battery with the output voltage of station
type BatteryIncompatibility frame
// ChargingSystemMalfunction Status flag indicating whether or not there is a problem with EV, such as improper connection
type ChargingSystemMalfunction frame
// ChargerStopControl Status flag indicating whether or not the station proceeds with shutdown process
type ChargerStopControl frame
func FromCanFrames(frames <-chan *CanFrame) <-chan ChademoEvent {
events := make(chan ChademoEvent)
@ -121,7 +137,7 @@ func FromCanFrames(frames <-chan *CanFrame) <-chan ChademoEvent {
events <- &MaxChargingTimeM{frame}
events <- &EstChargingTimeM{frame}
case CAN_ID_102:
events <- &ControlProtocolNumber{frame}
events <- &EVControlProtocolNumber{frame}
events <- &TargetBatteryVoltage{frame}
events <- &ChargingCurrentReq{frame}
events <- &ChargingRate{frame}
@ -135,6 +151,23 @@ func FromCanFrames(frames <-chan *CanFrame) <-chan ChademoEvent {
events <- &BattCurrentDeviationErr{frame}
events <- &HighBattTemperature{frame}
events <- &BattVoltageDeviationErr{frame}
case CAN_ID_108:
events <- &EVcontWeldingDetectSupport{frame}
events <- &AvailableOutputVoltage{frame}
events <- &AvailableOutputCurrent{frame}
events <- &ThresholdVoltage{frame}
case CAN_ID_109:
events <- &EVSEControlProtocolNumber{frame}
events <- &OutputVoltage{frame}
events <- &OutputCurrent{frame}
events <- &RemainingChargingTimeS{frame}
events <- &RemainingChargingTimeM{frame}
events <- &StationStatus{frame}
events <- &StationMalfunction{frame}
events <- &VehicleConnectorLock{frame}
events <- &BatteryIncompatibility{frame}
events <- &ChargingSystemMalfunction{frame}
events <- &ChargerStopControl{frame}
}
}
}()
@ -198,6 +231,41 @@ func bytesToUint16(bytes []uint8) uint16 {
return result
}
func evWeldingDetectionSupport(bytes *[]uint8) SuppVehicleWeldingDetection {
if (*bytes)[0] == 0 {
return VEHICLE_WELDING_DETECTION_NOT_SUPPORTED
}
return VEHICLE_WELDING_DETECTION_SUPPORTED
}
func getStationState(bytes *[]uint8) StationState {
if isBitSet((*bytes)[5], 0) {
return CHARGING
}
return STANDBY
}
func getConnectorLock(bytes *[]byte) ConnectorLock {
if isBitSet((*bytes)[5], 2) {
return CONNECTOR_LOCKED
}
return CONNECTOR_UNLOCKED
}
func getBatteryCompatibility(bytes *[]uint8) BatteryCompatible {
if isBitSet((*bytes)[5], 3) {
return BATTERY_INCOMPATIBLE
}
return BATTERY_COMPATIBLE
}
func getChargerStopControl(bytes *[]uint8) StopControl {
if isBitSet((*bytes)[5], 5) {
return SHUTDOWN_STOP
}
return OPERATING
}
func (m MaximumBatteryVoltage) GetValue() (interface{}, *string) {
return bytesToUint16(m.payload[4:6]), &m.date
}
@ -222,7 +290,7 @@ func (e EstChargingTimeM) GetValue() (interface{}, *string) {
return e.payload[3], &e.date
}
func (c ControlProtocolNumber) GetValue() (interface{}, *string) {
func (c EVControlProtocolNumber) GetValue() (interface{}, *string) {
return c.payload[0], &c.date
}
@ -278,57 +346,62 @@ func (b BattVoltageDeviationErr) GetValue() (interface{}, *string) {
return getFault(&b.payload, 4, 4), &b.date
}
func (v VehicleChargingEnabled) String() string {
switch v {
case DISABLED:
return "disabled"
case ENABLED:
return "enabled"
default:
panic("VehicleChargingEnabled not implemented")
}
func (e EVcontWeldingDetectSupport) GetValue() (interface{}, *string) {
return evWeldingDetectionSupport(&e.payload), &e.date
}
func (s ShiftLeverPosition) String() string {
switch s {
case PARKING:
return "parking"
case OTHER:
return "other"
default:
panic("ShiftLeverPosition not implemented")
}
func (a AvailableOutputVoltage) GetValue() (interface{}, *string) {
return bytesToUint16(a.payload[1:3]), &a.date
}
func (s SystemFault) String() string {
switch s {
case NORMAL:
return "normal"
case FAULT:
return "fault"
default:
panic("SystemFault not implemented")
}
func (a AvailableOutputCurrent) GetValue() (interface{}, *string) {
return a.payload[3], &a.date
}
func (c ContactorVehicleStatus) String() string {
switch c {
case CONTACTOR_CLOSED:
return "contactor closed"
case CONTACTOR_OPEN:
return "contactor open"
default:
panic("ContactorVehicleStatus not implemented")
}
func (t ThresholdVoltage) GetValue() (interface{}, *string) {
return bytesToUint16(t.payload[4:6]), &t.date
}
func (s StopRequest) String() string {
switch s {
case NO_REQUEST:
return "no request"
case STOP_REQUEST:
return "stop request"
default:
panic("StopRequest not implemented")
}
func (e EVSEControlProtocolNumber) GetValue() (interface{}, *string) {
return e.payload[0], &e.date
}
func (o OutputVoltage) GetValue() (interface{}, *string) {
return bytesToUint16(o.payload[1:3]), &o.date
}
func (o OutputCurrent) GetValue() (interface{}, *string) {
return o.payload[3], &o.date
}
func (r RemainingChargingTimeS) GetValue() (interface{}, *string) {
return r.payload[6] * 10, &r.date
}
func (r RemainingChargingTimeM) GetValue() (interface{}, *string) {
return r.payload[7], &r.date
}
func (s StationStatus) GetValue() (interface{}, *string) {
return getStationState(&s.payload), &s.date
}
func (s StationMalfunction) GetValue() (interface{}, *string) {
return getFault(&s.payload, 5, 1), &s.date
}
func (v VehicleConnectorLock) GetValue() (interface{}, *string) {
return getConnectorLock(&v.payload), &v.date
}
func (b BatteryIncompatibility) GetValue() (interface{}, *string) {
return getBatteryCompatibility(&b.payload), &b.date
}
func (c ChargingSystemMalfunction) GetValue() (interface{}, *string) {
return getFault(&c.payload, 5, 4), &c.date
}
func (c ChargerStopControl) GetValue() (interface{}, *string) {
return getChargerStopControl(&c.payload), &c.date
}

@ -0,0 +1,71 @@
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
)
// (2022-07-08 16:54:15.587099) can0 100 [8] 00 00 00 00 00 00 64 00
const expr = "\\((.*)\\)\\s+can\\d+\\s+(\\d+)\\s+\\[\\d\\]\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)"
const length = 11
var pattern = regexp.MustCompile(expr)
func Readfile(filename *string) <-chan *CanFrame {
c := make(chan *CanFrame)
go func() {
defer close(c)
file, err := os.Open(*filename)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := scanner.Text()
c <- fromString(&text)
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
}
}()
return c
}
func fromString(text *string) *CanFrame {
submatch := pattern.FindStringSubmatch(*text)
if len(submatch) == length {
canid, err := strconv.ParseUint(submatch[2], 16, 0)
if err != nil {
return nil
}
payload := make([]uint8, 8)
for idx, octet := range submatch[3:] {
val, err := strconv.ParseUint(octet, 16, 0)
if err != nil {
fmt.Println(err)
continue
}
payload[idx] = uint8(val)
}
return &CanFrame{
date: submatch[1],
canid: uint32(canid),
payload: payload,
}
}
return nil
}

@ -22,7 +22,7 @@ func TestFromString(t *testing.T) {
name: "Candump parsing test",
args: args{text: &dump},
want: &CanFrame{
canid: 100,
canid: CAN_ID_100,
date: "2022-07-08 16:54:15.587099",
payload: []uint8{0, 0, 0, 0, 0, 0, 0x64, 0},
},
@ -30,8 +30,8 @@ func TestFromString(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FromString(tt.args.text); !reflect.DeepEqual(got, tt.want) {
t.Errorf("FromString() = %v, want %v", got, tt.want)
if got := fromString(tt.args.text); !reflect.DeepEqual(got, tt.want) {
t.Errorf("fromString() = %v, want %v", got, tt.want)
}
})
}

@ -1,3 +1,5 @@
module chademo-log
go 1.18
require golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f

@ -0,0 +1,175 @@
package main
const (
CAN_ID_100 = 0x100
CAN_ID_101 = 0x101
CAN_ID_102 = 0x102
CAN_ID_108 = 0x108
CAN_ID_109 = 0x109
DISABLED VehicleChargingEnabled = iota
ENABLED
PARKING ShiftLeverPosition = iota
OTHER
NORMAL SystemFault = iota
FAULT
CONTACTOR_CLOSED ContactorVehicleStatus = iota
CONTACTOR_OPEN
NO_REQUEST StopRequest = iota
STOP_REQUEST
VEHICLE_WELDING_DETECTION_SUPPORTED SuppVehicleWeldingDetection = iota
VEHICLE_WELDING_DETECTION_NOT_SUPPORTED
STANDBY StationState = iota
CHARGING
CONNECTOR_LOCKED ConnectorLock = iota
CONNECTOR_UNLOCKED
BATTERY_COMPATIBLE BatteryCompatible = iota
BATTERY_INCOMPATIBLE
OPERATING StopControl = iota
SHUTDOWN_STOP
)
type VehicleChargingEnabled uint8
type ShiftLeverPosition uint8
type SystemFault uint8
type ContactorVehicleStatus uint8
type StopRequest uint8
type SuppVehicleWeldingDetection uint8
type StationState uint8
type ConnectorLock uint8
type BatteryCompatible uint8
type StopControl uint8
type CanFrame struct {
canid uint32
payload []uint8
date string
}
func (v VehicleChargingEnabled) String() string {
switch v {
case DISABLED:
return "disabled"
case ENABLED:
return "enabled"
default:
panic("VehicleChargingEnabled not implemented")
}
}
func (s ShiftLeverPosition) String() string {
switch s {
case PARKING:
return "parking"
case OTHER:
return "other"
default:
panic("ShiftLeverPosition not implemented")
}
}
func (s SystemFault) String() string {
switch s {
case NORMAL:
return "normal"
case FAULT:
return "fault"
default:
panic("SystemFault not implemented")
}
}
func (c ContactorVehicleStatus) String() string {
switch c {
case CONTACTOR_CLOSED:
return "contactor closed"
case CONTACTOR_OPEN:
return "contactor open"
default:
panic("ContactorVehicleStatus not implemented")
}
}
func (s StopRequest) String() string {
switch s {
case NO_REQUEST:
return "no request"
case STOP_REQUEST:
return "stop request"
default:
panic("StopRequest not implemented")
}
}
func (s SuppVehicleWeldingDetection) String() string {
switch s {
case VEHICLE_WELDING_DETECTION_NOT_SUPPORTED:
return "not supported"
case VEHICLE_WELDING_DETECTION_SUPPORTED:
return "supported"
default:
panic("SuppVehicleWeldingDetection not implemented")
}
}
func (s StationState) String() string {
switch s {
case CHARGING:
return "charging"
case STANDBY:
return "standby"
default:
panic("StationState not implemented")
}
}
func (c ConnectorLock) String() string {
switch c {
case CONNECTOR_LOCKED:
return "locked"
case CONNECTOR_UNLOCKED:
return "unlocked"
default:
panic("ConnectorLock not implemented")
}
}
func (b BatteryCompatible) String() string {
switch b {
case BATTERY_COMPATIBLE:
return "compatible"
case BATTERY_INCOMPATIBLE:
return "incompatible"
default:
panic("BatteryCompatible not implemented")
}
}
func (s StopControl) String() string {
switch s {
case OPERATING:
return "operating"
case SHUTDOWN_STOP:
return "shutdown or stop charging"
default:
panic("StopControl not implemented")
}
}
Loading…
Cancel
Save