diff --git a/certificate.go b/certificate.go index 18a0865..a6d8c60 100644 --- a/certificate.go +++ b/certificate.go @@ -23,7 +23,7 @@ const ( type CertificateConnectRequest struct { Net NetConnectionType `json:"NET"` - SerialNumber string `json:"DNSO"` + SerialNumber string `json:"DSNO"` DeviceName string `json:"DEVNAME"` ChannelsNumber uint `json:"CHANNEL"` LicensePlate string `json:"CARNUM"` @@ -35,6 +35,25 @@ type CertificateConnectRequest struct { EvidenceSupport string `json:"EV"` } +type CertificateConnectResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` + CommandMask uint `json:"MASKCMD"` +} + +type CommandMaskParameters uint + +const ( + CommandMaskAlarm = 1 << iota + CommandMaskScanningGun + CommandMaskPassengerFlow + CommandMaskFaceContrast + CommandMaskCard + CommandMaskShutdownReport + CommandMaskGPSReport + CommandMaskAll = 0b1111111 +) + /* func (e *Package) RequestConnect(session string, serial string, numOfCams int) { e.Payload = map[string]any{ diff --git a/cmd/dev-server/main.go b/cmd/dev-server/main.go new file mode 100644 index 0000000..fc624f3 --- /dev/null +++ b/cmd/dev-server/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "fmt" + "gitea.unprism.ru/KRBL/n9m" + "net" + "os" +) + +func main() { + ln, err := net.Listen("tcp", "0.0.0.0:5556") + + if err != nil { + panic(err) + } + + for { + var conn net.Conn + + conn, err = ln.Accept() + + if err != nil { + continue + } + + go handle(conn) + } +} + +func handle(conn net.Conn) { + var err error + var rn int + + var serial string + + packS := n9m.Package{} + tmp := make([]byte, 1024) + + for { + rn, err = conn.Read(tmp) + + if err != nil { + fmt.Println(err) + return + } + + packS.AddToAccum(tmp[:rn]) + + for packS.ReadPackage() { + switch packS.PayloadType { + case n9m.PayloadTypeData: + combined := packS.Payload.Module + ":" + packS.Payload.Operation + + switch combined { + case "CERTIFICATE:CONNECT": + var params n9m.CertificateConnectRequest + + if err = packS.GetParametersAs(¶ms); err != nil { + fmt.Println(combined, err) + return + } + + var response = n9m.CertificateConnectResponse{ + ErrorCode: 0, + CommandMask: n9m.CommandMaskAll, + } + + packS.SetResponse(response) + + if _, err = conn.Write(packS.PackPackage()); err != nil { + fmt.Println(combined, err) + return + } + + fmt.Println("Connected:", params.SerialNumber) + serial = params.SerialNumber + + var request n9m.ConfigModelGetRequest + request.MDVR = "?" + packS.Payload.Module = "CONFIGMODEL" + packS.Payload.Operation = "GET" + packS.SetParameters(request) + + conn.Write(packS.PackPackage()) + + /* + go func() { + pack := packS + pack.Payload.Module = "EVEM" + pack.Payload.Operation = "GALARMING" + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { + fmt.Println("Sent!") + if _, err := conn.Write(pack.PackPackage()); err != nil { + fmt.Println("Failed to send GALARMING:", err) + return + } + } + }() + */ + case "CERTIFICATE:KEEPALIVE": + packS.SetResponse(nil) + if _, err = conn.Write(packS.PackPackage()); err != nil { + fmt.Println(combined, err) + return + } + + fmt.Println("Connection is still alive!") + case "EVEM:GGALARMING": + fmt.Println(string(packS.RawPayload)) + + var response n9m.EventModelGetAlarmingResponse + if err = packS.GetResponseAs(&response); err != nil { + fmt.Println(combined, err) + continue + } + + fmt.Printf("%+v\n", response) + case "CONFIGMODEL:GET": + os.WriteFile(fmt.Sprintf("./%s.json", serial), packS.RawPayload, 0644) + case "EVEM:SENDALARMINFO": + var params n9m.SendAlarmInfoParameters + var response n9m.SendAlarmInfoResponse + + if err = packS.GetParametersAs(¶ms); err != nil { + fmt.Printf("Error: %s\nData: %s", err, packS.RawPayload) + continue + } + + response.ErrorCode = 0 + response.AlarmType = params.AlarmType + response.CommandType = params.CommandType + response.AlarmUID = params.AlarmUID + response.NumberOfRestarts = params.NumberOfRestarts + response.InstructionSerial = params.InstructionSerial + + switch params.AlarmType { + case n9m.AlarmTypeMotionDetection: + break + case n9m.AlarmTypeVideoLoss, n9m.AlarmTypeCameraCovered: + var cameraParams n9m.SendAlarmInfoCameraParameters + + if err = packS.GetParametersAs(&cameraParams); err != nil { + fmt.Printf("Error: %s\nData: %s", err, packS.RawPayload) + continue + } + + fmt.Printf("%+v\n", cameraParams) + + packS.SetResponse(response) + conn.Write(packS.PackPackage()) + default: + fmt.Println("Unknown alarm type:", params.AlarmType) + } + default: + fmt.Println("Strange operation:", combined) + } + + case n9m.PayloadTypeSpecial: + switch packS.SSRC { + case n9m.SpecialPayloadTypeGPS: + fmt.Printf("%+v\n", packS.GPS) + default: + fmt.Println("Unhandled special operation:", packS.SSRC) + } + default: + fmt.Println("Unhandled operation:", packS.PayloadType) + } + } + } +} diff --git a/configmodel.go b/configmodel.go index 90835c2..3c74e92 100644 --- a/configmodel.go +++ b/configmodel.go @@ -28,37 +28,41 @@ func (e *Package) SetParameters(params map[string]any, serial int, session strin } // log.Println(e.Payload) } // end of 'SetParameters' function +*/ -// todo al1 -func (e *Package) ConfigeModel(Sid string) { - e.Payload = map[string]any{ - "MODULE": "CONFIGMODEL", - "OPERATION": "SET", - "PARAMETER": map[string]any{ - "MDVR": map[string]any{ - "KEYS": map[string]any{ // KEY parameters - "GV": 1, // GPS version - }, - "PGDSM": map[string]any{ // Network monitoring status parameters - "PGPS": map[string]any{ // GPS position - "EN": 1, // Real-time position monitoring - "TM": 10, // Time interval - }, - }, - "PSI": map[string]any{ // Platform basic information - "CG": map[string]any{ // Call information - "AS": 0, // Automatic answer - }, - }, - "SUBSTRNET": map[string]any{ - "SM": 1, // 0-Smooth .. 4-Clear - }, - }, - }, - } +type ConfigModelSetRequest struct { + MDVR Setting `json:"MDVR"` } -// main server util +type ConfigModelGetRequest struct { + MDVR interface{} `json:"MDVR"` +} + +type ConfigModelSetResponse struct { + MDVR Setting `json:"MDVR"` +} + +func (e *Package) InitialConfigModelSetRequest() { + e.SetParameters(ConfigModelSetRequest{ + MDVR: Setting{ + KEYS: KEYS{ + GV: 1, // GPS version + }, + PGDSM: PGDSM{ + PGPS: PGPS{ + EN: 1, // Real-time position monitoring + MODE: 0b10, // Enable timer + TM: 10, // Time interval + }, + }, + SUBSTRNET: SUBSTRNET{ + SM: 1, + }, + }, + }) +} + +/* func (e *Package) ResponseConfigModelSet(Sid string) { e.Payload = map[string]any{ "MODULE": "CONFIGMODUL", // it's not error diff --git a/evem.go b/evem.go index 0cde99a..d382e27 100644 --- a/evem.go +++ b/evem.go @@ -1,5 +1,174 @@ package n9m +// 3.4.1.3 +type EventModelGetAlarmStatusInfoResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` + StorageErrors []StorageErrorStatus `json:"ST"` + AnotherStorageErrors []AnotherStorageErrorStatus `json:"VS"` + VideoLossErrors []VideoLossErrorStatus `json:"VL"` + GPSError GPSErrorStatus `json:"GFA"` + GPSAntennaError GPSAntennaErrorStatus `json:"GPSS"` +} + +// 3.4.1.4.21 +type StorageErrorStatus struct { + CameraCovered uint `json:"ISA"` + ChannelBind uint `json:"LHC"` +} + +// 3.4.1.4.4 +type AnotherStorageErrorStatus struct { +} + +// 3.4.1.4.5 +type VideoLossErrorStatus struct { +} + +// 3.4.1.4.44 +type GPSErrorStatus struct { +} + +// 3.4.1.4.46 +type GPSAntennaErrorStatus struct { +} + +// 3.4.1.5 +// Alarm upload +type SendAlarmInfoParameters struct { + AlarmType AlarmType `json:"ALARMTYPE"` + CommandType uint `json:"CMDTYPE"` + AlarmUID uint `json:"ALARMUID"` + NumberOfRestarts uint `json:"RUN"` + AlarmLevel AlarmLevel `json:"ALARMAS"` + AlarmCount uint `json:"ALARMCOUNT"` + TriggerType TriggerType `json:"TRIGGERTYPE"` + ContinueTime uint `json:"CONTINUETIME"` + CurrentTime uint `json:"CURRENTTIME"` + Language Language `json:"L"` + GPSData GPSData `json:"P"` + RealTimeUpload uint `json:"REAL"` + InstructionSerial uint `json:"CMDNO"` +} + +// 3.4.1.5 +// Alarm upload +type SendAlarmInfoResponse struct { + ErrorCode uint `json:"ERRORCODE"` + AlarmType AlarmType `json:"ALARMTYPE"` + ErrorCause string `json:"ERRORCAUSE"` + CommandType uint `json:"CMDTYPE"` + AlarmUID uint `json:"ALARMUID"` + NumberOfRestarts uint `json:"RUN"` + InstructionSerial uint `json:"CMDNO"` +} + +// 3.4.1.5.1 +type SendAlarmInfoCameraParameters struct { + SendAlarmInfoParameters + + Channel uint `json:"CHANNEL"` + ChannelMask uint `json:"CHANNELMASK"` + LCH []uint `json:"LCH"` + Push uint `json:"PUSH"` + AlarmName string `json:"ALARMNAME"` + AlarmAbbreviation string `json:"SER"` +} + +type AlarmType uint + +const ( + AlarmTypeVideoLoss AlarmType = iota + AlarmTypeCameraCovered + AlarmTypeMotionDetection + AlarmTypeStorageAbnormal + AlarmTypeUserDefined + AlarmTypeSentriesInspection + AlarmTypeViolation + AlarmTypeEmergency + AlarmTypeSpeedAlarm + AlarmTypeLowVoltage + AlarmTypeOutOfFence = iota + 7 + AlarmTypeAccAlarm + AlarmTypePeripheralsDropped + AlarmTypeStopAnnouncement + AlarmTypeGpsAntenna + AlarmTypeDayNightSwitch + AlarmTypeProhibitDriving + AlarmTypeSerialAlarm = iota + 15 + AlarmTypeFatigueAlarm + AlarmTypeTakeOutParking + AlarmTypeGestureAlarm + AlarmTypeGreenDriving + AlarmTypeIllegalIgnition + AlarmTypeIllegalShutdown + AlarmTypeCustomExternal + AlarmTypeThinkingLKJ + AlarmTypeTAX3 + AlarmTypeOilAlarm + AlarmTypeBusLineOccupation + AlarmTypeForgottenAlarm + AlarmTypeSpecialCustomerFault + AlarmTypeTemperatureAbnormal + AlarmTypeTemperatureChangeAbnormal + AlarmTypeSmokeAlarm + AlarmTypeGBox + AlarmTypeLicensePlateRecognition + AlarmTypeAnotherSpeedAlarm + AlarmTypeWirelessSignalAbnormal + AlarmTypeArming + AlarmTypePhoneCall + AlarmTypeGPSFault + AlarmTypeDSMFault + AlarmTypeFireBox +) + +type AlarmLevel uint + +const ( + AlarmLevelImportant AlarmLevel = iota + AlarmLevelGeneral + AlarmLevelEmergency +) + +type TriggerType uint + +const ( + TriggerTypeManual TriggerType = iota + TriggerTypeAutomatic +) + +type Language uint + +const ( + LanguageSimplifiedChinese Language = iota + LanguageEnglish + LanguageKorean + LanguageItalian + LanguageGerman + LanguageThai + LanguageTurkey + LanguagePortugal + LanguageSpain + LanguageRomania + LanguageGreece + LanguageFrench + LanguageRussian + LanguageDutch + LanguageHebrew + LanguageChineseTraditional +) + +type EventModelGetAlarmingResponse struct { + CameraCoveredChannelMask uint `json:"VS_CH"` + CameraCoveredAlarmMask uint `json:"VS_AT"` + CameraCoveredStatusMask uint `json:"VS_AS"` + + VideoLossChannelMask uint `json:"VL_CH"` + VideoLossAlarmMask uint `json:"VL_AT"` + VideoLossStatusMask uint `json:"VL_AS"` +} + /* // main server util func (e *Package) ResponseAlarm(alarmType int64, alarmUID int64, cmdno int64, cmdtype int64, run int64, serial string, Sid string) { diff --git a/io.go b/io.go index d3305e2..fc92e3a 100644 --- a/io.go +++ b/io.go @@ -2,10 +2,12 @@ package n9m import ( "bytes" + "encoding/binary" "encoding/json" - "log" - + "fmt" "github.com/icza/bitio" + "log" + "time" ) // Read package @@ -20,7 +22,7 @@ func (e *Package) ReadPackage() bool { e.CompressFlag = r.TryReadBool() e.CSRCCount = uint8(r.TryReadBits(4)) e.PayloadType = PayloadType(r.TryReadBits(8)) - e.SSRC = uint16(r.TryReadBits(16)) + e.SSRC = SpecialPayloadType((r.TryReadBits(8) | (r.TryReadBits(8) << 8))) if e.EncryptionFlag && e.CompressFlag { // TODO: get snippet, that use this code @@ -60,11 +62,34 @@ func (e *Package) ReadPackage() bool { e.Accum = e.Accum[12+e.payloadLen:] - if e.PayloadType == PayloadTypeData { + switch e.PayloadType { + case PayloadTypeData: if err := json.Unmarshal(e.RawPayload, &e.Payload); err != nil { log.Printf("Error parsing JSON payload: %v", err) return false } + case PayloadTypeSpecial: + switch e.SSRC { + case SpecialPayloadTypeGPS: + e.GPS.GPSStatus = e.RawPayload[0] + e.GPS.Expand = e.RawPayload[1] + e.GPS.Real = e.RawPayload[2] + + e.GPS.Longitude = float64(binary.BigEndian.Uint32(e.RawPayload[4:8])) / 1e6 + e.GPS.Latitude = float64(binary.BigEndian.Uint32(e.RawPayload[8:12])) / 1e6 + e.GPS.Speed = float64(binary.BigEndian.Uint32(e.RawPayload[12:16])) / 100 + e.GPS.Direction = float64(binary.BigEndian.Uint32(e.RawPayload[16:20])) / 100 + e.GPS.Altitude = int32(binary.BigEndian.Uint32(e.RawPayload[20:24])) + + var err error + if e.GPS.Time, err = time.Parse("20060102150405", string(e.RawPayload[24:38])); err != nil { + log.Printf("Error parsing time: %v", err) + } + default: + fmt.Println("N9M parser warning: unknown special payload type", e.SSRC) + } + default: + fmt.Println("N9M parser warning: unknown payload type", e.PayloadType) } if r.TryError != nil { @@ -141,3 +166,23 @@ func (e *Package) GetParametersAs(parameters any) error { return json.Unmarshal(marshal, parameters) } + +func (e *Package) SetParameters(parameters any) { + e.Payload.Response = struct{}{} + e.Payload.Parameter = parameters +} + +func (e *Package) GetResponseAs(response any) error { + marshal, err := json.Marshal(e.Payload.Response) + + if err != nil { + return err + } + + return json.Unmarshal(marshal, response) +} + +func (e *Package) SetResponse(response any) { + e.Payload.Parameter = struct{}{} + e.Payload.Response = response +} diff --git a/params.go b/params.go index ff62345..9d19e49 100644 --- a/params.go +++ b/params.go @@ -1,5 +1,6 @@ package n9m +// 7.2 type RIP struct { DEVID string // ID номер устройства BN string // бортовой номер @@ -218,7 +219,8 @@ type DNS struct { } type KEYS struct { - MAC string // MAC-адрес + MAC string `json:"MAC,omitempty"` // MAC-адрес + GV uint `json:"GV,omitempty"` // GPS version } type WIFI struct { @@ -267,29 +269,67 @@ type SP struct { MUPORT int // UDP-порт медиасервера } -type Setting struct { - RIP RIP - VS VS - GSP GSP - TIMEP TIMEP - ETHERNET ETHERNET - KEYS KEYS - WIFI WIFI - M3G M3G - MCMS MCMS - ATP ATP - SSP SSP - SWUS SWUS - UMP UMP - SUBSTRNET SUBSTRNET - DOSD DOSD - AR AR - EOSD []EOSD - MAIN []VEC - IOP []IOP - SAP SAP - UAP UAP - PVLAS PVLAS - PMDAS PMDAS - DSM DSM +// 7.30 +type PGDSM struct { + PGPS PGPS `json:"PGPS,omitempty"` + PDSM PDSM `json:"PDSM,omitempty"` +} + +// 7.30.1 +// GPS (position) status parameter +type PGPS struct { + EN uint // Enable + MODE uint + SEP uint `json:"SEP,omitempty"` + TM uint `json:"TM,omitempty"` + NUM uint `json:"NUM,omitempty"` +} + +// 7.30.2 +type PDSM struct { +} + +// 7.39.3.9 +// Platform basic information +type PSI struct { + CG CG `json:"CG,omitempty"` +} + +// 7.39.3.8 +// Total record information for call +type CG struct { + ECL uint // The maximum calling time + ECML uint // The maximum calling time per month + AS uint // Terminal phonecall answering strategy + AT uint // Answer automatically when exceed time +} + +type Setting struct { + KEYS KEYS `json:"KEYS,omitempty"` + RIP RIP `json:"RIP,omitempty"` + TIMEP TIMEP `json:"TIMEP,omitempty"` + ATP ATP `json:"ATP,omitempty"` + SSP SSP `json:"SSP,omitempty"` + GSP GSP `json:"GSP,omitempty"` + UMP UMP `json:"UMP,omitempty"` + ETHERNET ETHERNET `json:"ETHERNET,omitempty"` + WIFI WIFI `json:"WIFI,omitempty"` + M3G M3G `json:"M3G,omitempty"` + MCMS MCMS `json:"MCMS,omitempty"` + SUBSTRNET SUBSTRNET `json:"SUBSTRNET,omitempty"` + DOSD DOSD `json:"DOSD,omitempty"` + AR AR `json:"AR,omitempty"` + MAIN []VEC `json:"MAIN,omitempty"` + EOSD []EOSD `json:"EOSD,omitempty"` + IOP []IOP `json:"IOP,omitempty"` + SAP SAP `json:"SAP,omitempty"` + UAP UAP `json:"UAP,omitempty"` + PGDSM PGDSM `json:"PGDSM,omitempty"` + PVLAS PVLAS `json:"PVLAS,omitempty"` + PMDAS PMDAS `json:"PMDAS,omitempty"` + VS VS `json:"VS,omitempty"` + PSI PSI `json:"PSI,omitempty"` + + SWUS SWUS `json:"SWUS,omitempty"` + DSM DSM `json:"DSM,omitempty"` } diff --git a/scheme.go b/scheme.go index 12b67ab..4f4922c 100644 --- a/scheme.go +++ b/scheme.go @@ -1,5 +1,12 @@ package n9m +import ( + "encoding/json" + "fmt" + "strconv" + "time" +) + type PayloadType uint8 const ( @@ -13,10 +20,28 @@ const ( PayloadTypeTransmissionSubStream PayloadType = 15 PayloadTypeRecordingSubStream PayloadType = 16 PayloadTypeBlackBox PayloadType = 17 - PayloadTypeGPS PayloadType = 22 + PayloadTypeSpecial PayloadType = 22 PayloadTypeMaintainData PayloadType = 30 ) +type SpecialPayloadType uint16 + +const ( + SpecialPayloadTypeHeartbeat SpecialPayloadType = iota + SpecialPayloadTypeHeartbeatWithoutBody + SpecialPayloadTypeGPS + SpecialPayloadTypeMileage + SpecialPayloadTypeEnvironmentalQuality + SpecialPayloadTypeDrivingPosture + SpecialPayloadTypeScanningGun + SpecialPayloadTypeOil + SpecialPayloadTypeGDS + SpecialPayloadTypeGPSToBWS + SpecialPayloadTypeCANBOX + SpecialPayloadTypeGSenor + SpecialPayloadTypeAckGPS +) + type Message struct { Module string `json:"MODULE"` Session string `json:"SESSION"` @@ -25,6 +50,74 @@ type Message struct { Response interface{} `json:"RESPONSE,omitempty"` } +// 3.4.5.27.1 +type GPSData struct { + GPSStatus uint8 + Expand uint8 + Real uint8 + Longitude float64 + Latitude float64 + Speed float64 + Direction float64 + Altitude int32 + Time time.Time +} + +func (g *GPSData) MarshalJSON() ([]byte, error) { + var alias struct { + GPSStatus uint8 `json:"V"` + Longitude string `json:"J"` + Latitude string `json:"W"` + Speed uint `json:"S"` + Direction uint `json:"C"` + Altitude int32 `json:"H"` + Time string `json:"T"` + } + + alias.GPSStatus = g.GPSStatus + alias.Longitude = fmt.Sprintf("%.6f", g.Longitude) + alias.Latitude = fmt.Sprintf("%.6f", g.Latitude) + alias.Speed = uint(g.Speed * 100) + alias.Direction = uint(g.Direction * 100) + alias.Altitude = g.Altitude + alias.Time = g.Time.Format("20060102150405") + + return json.Marshal(alias) +} + +func (g *GPSData) UnmarshalJSON(data []byte) (err error) { + var alias struct { + GPSStatus uint8 `json:"V"` + Longitude string `json:"J"` + Latitude string `json:"W"` + Speed uint `json:"S"` + Direction uint `json:"C"` + Altitude int32 `json:"H"` + Time string `json:"T"` + } + + if err = json.Unmarshal(data, &alias); err != nil { + return + } + + g.GPSStatus = alias.GPSStatus + + if g.Longitude, err = strconv.ParseFloat(alias.Longitude, 64); err != nil { + return fmt.Errorf("invalid longitude: %w", err) + } + + if g.Latitude, err = strconv.ParseFloat(alias.Latitude, 64); err != nil { + return fmt.Errorf("invalid latitude: %w", err) + } + + g.Speed = float64(alias.Speed) / 100.0 + g.Direction = float64(alias.Direction) / 100.0 + g.Altitude = alias.Altitude + g.Time, _ = time.Parse("20060102150405", alias.Time) + + return nil +} + type Package struct { Version uint8 EncryptionFlag bool @@ -32,10 +125,12 @@ type Package struct { CSRCCount uint8 PayloadType PayloadType - SSRC uint16 + SSRC SpecialPayloadType Reserved uint64 CSRC [16]uint64 + GPS GPSData + payloadLen uint64 Payload Message RawPayload []byte