274 lines
10 KiB
Go
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) | | |
|
|
*/
|