Files
hamview/schema/meshcore.go
maze 13afa08e8a
Some checks failed
Test and build / Test and lint (push) Failing after 36s
Test and build / Build collector (push) Failing after 43s
Test and build / Build receiver (push) Failing after 42s
Checkpoint
2026-03-05 15:38:18 +01:00

274 lines
10 KiB
Go

package schema
import (
"context"
"encoding/hex"
"errors"
"fmt"
"os"
"time"
"xorm.io/builder"
"git.maze.io/go/ham/protocol"
"git.maze.io/go/ham/protocol/meshcore"
)
func init() {
RegisterModel(new(MeshCorePacket))
RegisterModel(new(MeshCoreNode))
RegisterModel(new(MeshCoreNodePosition))
RegisterModel(new(MeshCoreGroup))
}
type MeshCorePacket struct {
ID int64 `xorm:"pk autoincr" json:"id"`
RadioID int64 `xorm:"index" json:"radio_id"`
Radio *Radio `xorm:"-" json:"radio"`
SNR float64 `xorm:"not null default 0" json:"snr"`
RSSI int `xorm:"not null default 0" json:"rssi"`
Version int `xorm:"not null default 1" json:"version"`
RouteType uint8 `xorm:"index not null" json:"route_type"`
PayloadType uint8 `xorm:"index not null" json:"payload_type"`
Hash string `xorm:"varchar(16) index not null" json:"hash"`
Path []byte `xorm:"bytea" json:"path"`
Payload []byte `xorm:"bytea not null" json:"payload"`
Raw []byte `xorm:"bytea not null" json:"raw"`
Parsed *string `xorm:"jsonb" json:"parsed"`
ChannelHash string `xorm:"varchar(2) index" json:"channel_hash,omitempty"`
ReceivedAt time.Time `json:"received_at"`
}
func (MeshCorePacket) TableName() string {
return "meshcore_packet"
}
func GetMeshCorePackets(ctx context.Context, limit int) ([]*MeshCorePacket, error) {
packets := make([]*MeshCorePacket, 0, limit)
return packets, Query(ctx).
OrderBy("`received_at` DESC").
Limit(limit).
Find(&packets)
}
func GetMeshCorePacketsByHash(ctx context.Context, hash string) ([]*MeshCorePacket, error) {
if len(hash) != 16 {
return nil, errors.New("invalid hash")
} else if _, err := hex.DecodeString(hash); err != nil {
return nil, err
}
packets := make([]*MeshCorePacket, 0, 10)
return packets, Query(ctx).
Where(builder.Eq{"hash": hash}).
OrderBy("`received_at` ASC").
Find(&packets)
}
func GetMeshCorePacketsByPayloadType(ctx context.Context, payloadType meshcore.PayloadType) ([]*MeshCorePacket, error) {
packets := make([]*MeshCorePacket, 0, 10)
return packets, Query(ctx).
Where(builder.Eq{"payload_type": int(payloadType)}).
OrderBy("`received_at` DESC").
Find(&packets)
}
func GetMeshCorePacketsByChannelHash(ctx context.Context, hash string) ([]*MeshCorePacket, error) {
packets := make([]*MeshCorePacket, 0, 10)
return packets, Query(ctx).
Where(builder.Eq{
"`payload_type`": int(meshcore.TypeGroupText),
"`channel_hash`": hash,
}).
OrderBy("`received_at` DESC").
Find(&packets)
}
type MeshCoreGroup struct {
ID int64 `xorm:"pk autoincr" json:"id"`
Name string `xorm:"varchar(32) not null unique" json:"name"`
Secret string `xorm:"varchar(32) not null" json:"secret"`
IsPublic bool `xorm:"boolean not null default false" json:"-"`
}
func (MeshCoreGroup) TableName() string {
return "meshcore_group"
}
func GetMeshCoreGroups(ctx context.Context) ([]*MeshCoreGroup, error) {
groups := make([]*MeshCoreGroup, 0, 10)
return groups, Query(ctx).
Where(builder.Eq{"is_public": true}).
OrderBy("name asc").
Find(&groups)
}
type MeshCoreNode struct {
ID int64 `xorm:"pk autoincr" json:"id"`
PacketHash string `xorm:"varchar(16) index 'meshcore_packet_hash'" json:"-"`
Packets []*MeshCorePacket `xorm:"-" json:"packet"`
Name string `xorm:"varchar(100) not null" json:"name"`
Type uint8 `xorm:"index not null" json:"type"`
Prefix string `xorm:"varchar(2) not null" json:"prefix"`
PublicKey string `xorm:"varchar(64) not null unique" json:"public_key"`
FirstHeardAt time.Time `xorm:"timestamp not null" json:"first_heard_at"`
LastHeardAt time.Time `xorm:"timestamp not null" json:"last_heard_at"`
LastLatitude *float64 `json:"last_latitude"`
LastLongitude *float64 `json:"last_longitude"`
Distance float64 `xorm:"-" json:"distance,omitempty"`
}
func (MeshCoreNode) TableName() string {
return "meshcore_node"
}
func GetMeshCoreNodeByPublicKey(ctx context.Context, publicKey string) (*MeshCoreNode, error) {
node := new(MeshCoreNode)
if ok, err := Query(ctx).
Where(builder.Eq{"public_key": publicKey}).
Get(node); err != nil {
return nil, err
} else if !ok {
return nil, os.ErrNotExist
}
return node, nil
}
func GetMeshCoreNodes(ctx context.Context) ([]*MeshCoreNode, error) {
nodes := make([]*MeshCoreNode, 0, 100)
return nodes, Query(ctx).
OrderBy("`last_heard_at` DESC").
Find(&nodes)
}
func GetMeshCoreNodesByType(ctx context.Context, nodeType meshcore.NodeType) ([]*MeshCoreNode, error) {
nodes := make([]*MeshCoreNode, 0, 100)
return nodes, Query(ctx).
Where(builder.Eq{"type": nodeType}).
OrderBy("`last_heard_at` DESC").
Find(&nodes)
}
type MeshCoreNodeWithDistance struct {
MeshCoreNode `xorm:"extends"`
Distance float64 `xorm:"distance"`
}
func GetMeshCoreNodesCloseTo(ctx context.Context, publicKey string, radius float64) (*MeshCoreNode, []*MeshCoreNode, error) {
node, err := GetMeshCoreNodeByPublicKey(ctx, publicKey)
if err != nil {
return nil, nil, err
} else if node.LastLatitude == nil || node.LastLongitude == nil {
return nil, nil, errors.New("node has no location")
}
nodesWithDistance := make([]*MeshCoreNodeWithDistance, 0, 100)
selectClause := fmt.Sprintf("*, "+
"ST_Distance("+
"ST_SetSRID(ST_MakePoint(%f, %f), 4326)::geography, "+
"ST_SetSRID(ST_MakePoint(last_longitude, last_latitude), 4326)::geography"+
") as distance",
*node.LastLongitude, *node.LastLatitude)
if err = Query(ctx).
Select(selectClause).
Where("id != ?", node.ID).
Where("ST_DWithin("+
"ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, "+
"ST_SetSRID(ST_MakePoint(last_longitude, last_latitude), 4326)::geography, ?)",
*node.LastLongitude, *node.LastLatitude, radius).
OrderBy("`distance` ASC").
Find(&nodesWithDistance); err != nil {
return nil, nil, err
}
nodes := make([]*MeshCoreNode, len(nodesWithDistance))
for i, node := range nodesWithDistance {
node.MeshCoreNode.Distance = node.Distance
nodes[i] = &node.MeshCoreNode
}
return node, nodes, nil
}
type MeshCoreNodePosition struct {
ID int64 `xorm:"pk autoincr" json:"id"`
MeshCoreNodeId int64 `xorm:"not null 'meshcore_node_id'" json:"-"`
MeshCoreNode *MeshCoreNode `xorm:"-" json:"node"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
ReceivedAt time.Time `xorm:"timestamp not null" json:"received_at"`
}
func (MeshCoreNodePosition) TableName() string {
return "meshcore_node_position"
}
type MeshCoreStats struct {
Messages int64 `json:"messages"`
Nodes int64 `json:"nodes"`
Receivers int64 `json:"receivers"`
Packets struct {
Timestamps []int64 `json:"timestamps"`
Packets []int64 `json:"packets"`
} `json:"packets"`
}
func GetMeshCoreStats(ctx context.Context) (*MeshCoreStats, error) {
var (
engine = Query(ctx)
stats = new(MeshCoreStats)
err error
)
if stats.Messages, err = engine.
In("`payload_type`", meshcore.TypeText, meshcore.TypeGroupText).
Count(&MeshCorePacket{}); err != nil {
return nil, err
}
if stats.Nodes, err = engine.
Count(&MeshCoreNode{}); err != nil {
return nil, err
}
if stats.Receivers, err = engine.
Where(builder.Eq{"`protocol`": protocol.MeshCore}).
Count(&Radio{}); err != nil {
return nil, err
}
return stats, nil
}
/*
Column | Type | Collation | Nullable | Default
--------------+--------------------------+-----------+----------+---------------------------------------------
id | bigint | | not null | nextval('meshcore_packet_id_seq'::regclass)
snr | real | | not null | 0
rssi | smallint | | not null | 0
hash | bytea | | not null |
route_type | smallint | | not null |
payload_type | smallint | | not null |
path | bytea | | |
payload | bytea | | |
raw | bytea | | |
parsed | jsonb | | |
received_at | timestamp with time zone | | not null | now()
created_at | timestamp with time zone | | not null | now()
*/
/*
id | bigint | | not null | nextval('meshcore_node_id_seq'::regclass)
last_advert_id | bigint | | |
node_type | smallint | | not null | 0
public_key | bytea | | not null |
name | text | | |
local_time | timestamp with time zone | | not null |
first_heard | timestamp with time zone | | not null | now()
last_heard | timestamp with time zone | | not null | now()
last_latitude | numeric(10,8) | | |
last_longitude | numeric(11,8) | | |
prefix | bytea | | | generated always as ("substring"(public_key, 0, 2)) stored
last_position | geometry(Point,4326) | | | generated always as ( +
| | | | CASE +
| | | | WHEN last_latitude IS NOT NULL AND last_longitude IS NOT NULL THEN st_setsrid(st_makepoint(last_latitude::double precision, last_longitude::double precision), 4326)+
| | | | ELSE NULL::geometry +
| | | | END) stored
position | geometry(Point,4326) | | |
*/