Small CLI monitoring tool to decode proprietary [...] protocol and display info in human readable representation
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ycli-mon/yabl/protocol.go

253 lines
6.4 KiB

package yabl
import (
can "cli-mon/can"
"encoding/binary"
time "time"
)
type action struct {
object Action
fields []*field
interval uint
name AName
}
type field struct {
length uint8
fromBus func(frame uint64) any
toBus func(value any) uint64
value uint64
last *time.Time
name FName
}
type key struct {
canId uint32
unitId uint
}
type converter struct {
tag string
protocolMap map[key]*action
fromBus <-chan *can.CanFrame
fromProducer <-chan *Event
toBus chan<- *can.CanFrame
toConsumer chan<- *Event
cpuPresentEnergyArray [CONNECTOR_MAX]*CpuPresentEnergy
cpuPeripheryInstance *CpuPeriphery
cpuEnergySettingsArray [CONNECTOR_MAX]*CpuEnergySettings
cpuErrorsInstance *CpuErrors
cpuDebugInstance *CpuDebug
puPresentEnergyArray [CONNECTOR_MAX]*PuPresentEnergy
puPeripheryArray [CONNECTOR_MAX]*PuPeriphery
puErrorsArray [CONNECTOR_MAX]*PuErrors
puDebugArray [CONNECTOR_MAX]*PuDebug
seccTargetEnergyArray [CONNECTOR_MAX]*SeccTargetEnergy
seccErrorsArray [CONNECTOR_MAX]*SeccErrors
logicAuthArray [CONNECTOR_MAX]*LogicAuth
logicEnergyMode [CONNECTOR_MAX]*LogicEnergyMode
logicErrorsInstance *LogicErrors
logicWorkingMode [CONNECTOR_MAX]*LogicWorkingMode
LogicCommunicationMode [CONNECTOR_MAX]*LogicCommunicationMode
contactorInternalStateArray [CONTACTOR_MAX]*ContactorInternalState
contactorInternalErrorsInstance *ContactorInternalErrors
contactorsInternalForce *ContactorsInternalForce
contactorInternalDebugArray [CONTACTOR_MAX]*ContactorInternalDebug
peripheryStateArray [CONNECTOR_MAX]*PeripheryState
peripheryInfoArray [CONNECTOR_MAX]*PeripheryInfo
peripheryDebugArray [CONNECTOR_MAX]*PeripheryDebug
converterPresentEnergyArray [CONVERTERS_MAX]*ConverterPresentEnergy
converterErrorsArray [CONVERTERS_MAX]*ConverterErrors
converterDebugArray [CONVERTERS_MAX]*ConverterDebug
}
type Converter interface {
FramesFromEvents([]*Event) ([]*can.CanFrame, bool)
EventsFromFrame(*can.CanFrame) ([]*Event, bool)
CheckTimeouts(isOffline bool) ([]*Event, bool)
}
func NewProtocolConverter(tag string) Converter {
return newConverter(tag)
}
func newConverter(tag string) *converter {
c := &converter{tag: tag}
c.initialize()
return c
}
func (c *converter) EventsFromFrame(frame *can.CanFrame) ([]*Event, bool) {
if frame == nil {
return nil, false
}
var unitId = uint((frame.CanId & UNIT_ID_MASK) >> UNIT_ID_OFFSET)
var canId = frame.CanId & ACTION_ID_MASK
var k = key{canId: canId, unitId: unitId}
if param, ok := c.protocolMap[k]; ok {
result := make([]*Event, 0, len(param.fields))
var start uint8 = 0
for _, f := range param.fields {
if val, found := getValueFromBuf(start, f.length, frame.Payload); found {
f.last = frame.Date
if f.value != val { // value changed
f.value = val
obj := f.fromBus(val)
event := &Event{
Tag: &c.tag,
Field: &f.name,
ActionName: &param.name,
Object: &param.object,
unit: unit(unitId),
Updated: frame.Date,
Value: obj,
}
result = append(result, event)
}
}
start += f.length // next field
}
return result, len(result) > 0
}
return nil, false
}
func getValueFromBuf(from uint8, size uint8, buffer *[]byte) (uint64, bool) {
value := binary.LittleEndian.Uint64(*buffer)
var mask uint64 = ^(ALL_BITS << size)
mask = mask << from
selected := mask & value
if selected == mask {
return 0, false
} else {
return selected >> from, true
}
}
func (u unit) GetUnitId() uint32 {
return uint32(u)
}
func (c *converter) CheckTimeouts(isOffline bool) ([]*Event, bool) {
var multipler uint = 2
var events []*Event
for k, param := range c.protocolMap {
var now time.Time
if isOffline {
now = getMaxTime(&c.protocolMap)
} else {
now = time.Now()
}
deadline := now.Add(-time.Duration(param.interval*multipler) * time.Millisecond)
var from uint8 = 0
for _, fld := range param.fields {
if fld.last == nil {
continue
} else if deadline.After(*fld.last) {
if events == nil {
events = make([]*Event, 0, len(param.fields))
}
event := &Event{
Tag: &c.tag,
Field: &fld.name,
ActionName: &param.name,
Object: &param.object,
unit: unit(k.unitId),
Updated: &deadline,
Value: nil,
}
var mask uint64 = ^(ALL_BITS << fld.length)
mask = mask << from
fld.value = mask
events = append(events, event)
fld.last = nil
}
from += fld.length
}
}
return events, events != nil
}
func getMaxTime(protocolMap *map[key]*action) time.Time {
now := time.Unix(0, 0)
for _, param := range *protocolMap {
for _, fld := range param.fields {
if fld.last != nil && fld.last.After(now) {
now = *fld.last
}
}
}
return now
}
func (c *converter) FramesFromEvents(events []*Event) ([]*can.CanFrame, bool) {
var keys = make([]*key, len(events))
for _, event := range events {
unitId := event.GetUnitId()
actionId := event.ActionName
fieldId := event.Field
if ok, act, fld := c.findField(unitId, actionId, fieldId); ok {
keys = append(keys, act)
fld.value = fld.toBus(event.Value)
}
}
var frames = make([]*can.CanFrame, len(events))
for _, k := range keys {
act := c.protocolMap[*k]
canId := (act.object.GetUnitId() & (UNIT_ID_MASK >> UNIT_ID_OFFSET)) << UNIT_ID_OFFSET
canId = canId | k.canId
frame := &can.CanFrame{
CanId: canId,
Payload: fieldsToPayload(act.fields),
}
frames = append(frames, frame)
}
return frames, len(frames) > 0
}
func fieldsToPayload(fields []*field) *[]uint8 {
var val uint64 = 0
var offset uint8 = 0
for _, f := range fields {
val = val | (f.value << offset)
offset += f.length
}
var buf = make([]uint8, 8)
binary.PutUvarint(buf, val)
return &buf
}
func (c *converter) findField(unitId uint32, actionId *AName, fieldId *FName) (ok bool, key *key, fld *field) {
for k, a := range c.protocolMap {
for _, f := range a.fields {
if k.unitId == uint(unitId) && a.name == *actionId && f.name == *fieldId {
return true, &k, f
}
}
}
return false, nil, nil
}