Added register page, email field, finished login + register UI, reduced the number of templates generated.
This commit is contained in:
@@ -5,10 +5,6 @@ DB_PASSWORD=1234
|
||||
DB_HOST=pagerino_db
|
||||
DB_NAME=pager_data
|
||||
|
||||
# App server specific settings
|
||||
# === Chirpstack API
|
||||
CHIRP_API_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjaGlycHN0YWNrIiwiaXNzIjoiY2hpcnBzdGFjayIsInN1YiI6IjI3YzI5Y2M2LTdjMTUtNDI5Yy1iMjBmLTczNzliZWYzYTI1ZCIsInR5cCI6ImtleSJ9.RsMPIGgPaGluBllRz0Ma_EthxUj3xM9pTPy_uUEAbvk
|
||||
|
||||
# === MQTT broker settings
|
||||
MQTT_ADDRESS=ssl://mosquitto:8883
|
||||
MQTT_CLIENT_ID=app-server
|
||||
@@ -16,7 +12,12 @@ MQTT_QOS=0
|
||||
# This App's ID in Chirpstack
|
||||
# + matches all IDs
|
||||
# # matches all IDs and all(!) subtopics
|
||||
APP_ID=d6ccd2ad-0cf7-46ab-8618-7a5a14b8676d
|
||||
#APP_ID=d6ccd2ad-0cf7-46ab-8618-7a5a14b8676d
|
||||
APP_ID=188c5d25-2d6b-43e3-aaf3-07dd0a8c386e
|
||||
|
||||
CHIRP_ADDRESS=chirpstack:8080
|
||||
CHIRP_API_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjaGlycHN0YWNrIiwiaXNzIjoiY2hpcnBzdGFjayIsInN1YiI6IjZlYTA1YTQ1LWUxODgtNDI3Yy1hMWMwLWZiNTQ1NWQ1N2QxZSIsInR5cCI6ImtleSJ9.i6as57Fod4m4oDvHw8LoV2wVz2wjnQFOi_l60zTnGy8
|
||||
SERVER_API_PORT=50222
|
||||
|
||||
# Enviroment variables that will be available for every application
|
||||
|
||||
@@ -28,7 +29,6 @@ LOG_PREFIX=[Pagerino]
|
||||
|
||||
# Main app container name
|
||||
SERVER_NAME=pagerino-app
|
||||
SERVER_API_PORT=50222
|
||||
|
||||
# General preferred connection timeout
|
||||
TIMEOUT=3s
|
||||
@@ -9,6 +9,10 @@ services:
|
||||
- app_net
|
||||
- pagerino_net
|
||||
restart: on-failure:3
|
||||
environment:
|
||||
CHIRP_ADDRESS: ${CHIRP_ADDRESS}
|
||||
SERVER_API_PORT: ${SERVER_API_PORT}
|
||||
APP_ID: ${APP_ID}
|
||||
|
||||
pagerino_db:
|
||||
image: postgres:15
|
||||
@@ -27,6 +31,19 @@ services:
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
|
||||
migrate:
|
||||
image: migrate/migrate
|
||||
networks:
|
||||
- app_net
|
||||
volumes:
|
||||
- ./migrations:/migrations
|
||||
command: [
|
||||
"-path", "/migrations",
|
||||
"-database", "postgres://${DB_USER}:${DB_PASSWORD}@${DB_NAME}:5432/pagerino_db?sslmode=disable",
|
||||
"up"
|
||||
]
|
||||
depends_on:
|
||||
- pagerino_db
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
@@ -68,28 +68,13 @@ const (
|
||||
|
||||
// AppData holds data and connections needed througout the program
|
||||
type AppData struct {
|
||||
ExitSig chan os.Signal // Use on fatal error to graciously exit, otherwise potential leaks
|
||||
DBPool *pgxpool.Pool // Execute queries on internal DB on this pool
|
||||
APIServer *grpc.Server // Provide API for apps
|
||||
MQTTClient mqtt.Client // Get Uplinks and downlinks
|
||||
ChirpGateClient chirp_api.GatewayServiceClient // Check gateway stats and manage them
|
||||
Timeout time.Duration // General connection/request timeout
|
||||
}
|
||||
|
||||
// Uplink reflects Chirpstack's uplink format
|
||||
type Uplink struct {
|
||||
ApplicationID string `json:"applicationID"`
|
||||
DevEUI string `json:"devEUI"`
|
||||
FCnt uint32 `json:"fCnt"`
|
||||
FPort uint8 `json:"fPort"`
|
||||
Data string `json:"data"` // Base64 payload
|
||||
RxInfo []struct {
|
||||
GatewayID string `json:"gatewayID"`
|
||||
Time string `json:"time"`
|
||||
RSSI int32 `json:"rssi"`
|
||||
SNR float64 `json:"snr"`
|
||||
} `json:"rxInfo"`
|
||||
DecodedPayload map[string]any `json:"decodedPayload,omitempty"`
|
||||
ExitSig chan os.Signal // Use on fatal error to graciously exit, otherwise potential leaks
|
||||
DBPool *pgxpool.Pool // Execute queries on internal DB on this pool
|
||||
APIServer *grpc.Server // Provide API for apps
|
||||
MQTTClient mqtt.Client // Get Uplinks and downlinks
|
||||
ChirpGateClient chirp_api.GatewayServiceClient // Check gateway stats and manage them
|
||||
ChirpDeviceCLient chirp_api.DeviceServiceClient // Check device stats
|
||||
Timeout time.Duration // General connection/request timeout
|
||||
}
|
||||
|
||||
// Downlink reflects Chirpstack's downlink format
|
||||
@@ -155,6 +140,49 @@ func MakeTLSConfig() (*tls.Config, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (data *AppData) printChirpStats() {
|
||||
ctx_tmout, cancel_ctx := context.WithTimeout(context.Background(), data.Timeout)
|
||||
defer cancel_ctx()
|
||||
|
||||
resp_gate, err := data.ChirpGateClient.List(ctx_tmout, &chirp_api.ListGatewaysRequest{
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
OrderByDesc: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Failed to get Chirpstack's API response: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Found the following gateways (first 10):")
|
||||
for idx, gate := range resp_gate.Result {
|
||||
log.Println(strconv.FormatInt(int64(idx), 10) + ": App[" +
|
||||
gate.TenantId + "] GateID[" + gate.GatewayId + "] Name[" +
|
||||
gate.Name + "]")
|
||||
}
|
||||
log.Println("[END]")
|
||||
|
||||
resp_device, err := data.ChirpDeviceCLient.List(ctx_tmout, &chirp_api.ListDevicesRequest{
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
OrderByDesc: true,
|
||||
ApplicationId: FindEnv("APP_ID"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Failed to get Chirpstack's API response: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Found the following devices (first 10):")
|
||||
for idx, dev := range resp_device.Result {
|
||||
log.Println(strconv.FormatInt(int64(idx), 10) + ": Name[" + dev.Name +
|
||||
"] Description:[" + dev.Description +
|
||||
"] DevEUI[" + dev.DevEui + "]",
|
||||
)
|
||||
}
|
||||
log.Println("[END]")
|
||||
}
|
||||
|
||||
// VerifyCredentials returns no error if password matches in DB
|
||||
func VerifyCredentials(username string, password string, data *AppData) error {
|
||||
if len(username) > MAX_CHAR_LEN || len(password) > MAX_CHAR_LEN {
|
||||
@@ -233,41 +261,59 @@ func makeMQTTHandler(data *AppData) mqtt.MessageHandler {
|
||||
log.Println("Uplink is responding in an unrecognized channel")
|
||||
}
|
||||
|
||||
//data.printChirpStats()
|
||||
|
||||
// Query timeout
|
||||
ctx, close := context.WithTimeout(context.Background(), data.Timeout)
|
||||
defer close()
|
||||
|
||||
// Get device IDs
|
||||
row := data.DBPool.QueryRow(ctx,
|
||||
"SELECT id FROM Devices WHERE euid = '$1'",
|
||||
"SELECT id FROM \"Devices\" WHERE euid = '$1';",
|
||||
uplink.DevEUI,
|
||||
)
|
||||
|
||||
var dev_id int32
|
||||
if err = row.Scan(&dev_id); err != nil {
|
||||
log.Println(ErrNotFound.Error() + ": Device.euid = " + uplink.DevEUI)
|
||||
if err == pgx.ErrNoRows {
|
||||
log.Println(
|
||||
"Adding new device to DB with DeviceEUI[" + uplink.DevEUI +
|
||||
"] and Name[" + uplink.DeviceName + "]",
|
||||
)
|
||||
data.DBPool.Exec(ctx,
|
||||
"INSERT INTO \"Devices\" (euid, name) VALUES ($1, $2)",
|
||||
uplink.DevEUI, uplink.DeviceName,
|
||||
)
|
||||
|
||||
} else {
|
||||
log.Println(err.Error() + ": Device.euid = " + uplink.DevEUI)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add Message to DB
|
||||
_, err = data.DBPool.Exec(ctx,
|
||||
"INSERT INTO Messages (sender_id, receiver_id, payload) VALUES ($1, $2, $3)",
|
||||
"INSERT INTO \"Messages\" (sender_id, receiver_id, payload) VALUES ($1, $2, $3);",
|
||||
dev_id, nil, decoded,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println(ErrModify.Error() + ": Message")
|
||||
log.Println(ErrModify.Error() + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get decoded custom payload
|
||||
pager_msg, err := serder.Deserialize(decoded)
|
||||
if err != nil {
|
||||
log.Println(err.Error() + ": deserialization")
|
||||
log.Println("Deserialization error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Received Pagerino payload for AppID[", pager_msg.AppID, "]:")
|
||||
log.Println(
|
||||
"Received Pagerino payload for AppID[" +
|
||||
strconv.FormatUint(uint64(pager_msg.AppID), 10) + "]:",
|
||||
)
|
||||
for _, field := range pager_msg.Fields {
|
||||
log.Println(field)
|
||||
}
|
||||
@@ -354,7 +400,7 @@ Expected: [number]{h|m|s|ms|us|ns}...`)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Created MQTT broker connection.")
|
||||
log.Println("Created MQTT broker connection at: " + FindEnv("MQTT_ADDRESS"))
|
||||
defer client.Disconnect(150)
|
||||
|
||||
Data.MQTTClient = client
|
||||
@@ -378,7 +424,7 @@ Expected: [number]{h|m|s|ms|us|ns}...`)
|
||||
// Serve gRPC
|
||||
go func() {
|
||||
if err := grpc_server.Serve(net_listen); err != nil {
|
||||
log.Println("Failed to serve gRPC API")
|
||||
log.Println("Failed to serve gRPC API: " + err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
@@ -391,42 +437,23 @@ Expected: [number]{h|m|s|ms|us|ns}...`)
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), // remove this when using TLS
|
||||
}
|
||||
|
||||
grpc_conn, err := grpc.Dial("chirpstack:8080", dialOpts...)
|
||||
grpc_conn, err := grpc.Dial(FindEnv("CHIRP_ADDRESS"), dialOpts...)
|
||||
if err != nil {
|
||||
log.Println("Failed to create a gRPC connection to Chirpstack's API: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
Data.ChirpGateClient = chirp_api.NewGatewayServiceClient(grpc_conn)
|
||||
Data.ChirpDeviceCLient = chirp_api.NewDeviceServiceClient(grpc_conn)
|
||||
defer grpc_conn.Close()
|
||||
|
||||
log.Println("Created Chirpstack's API connection.")
|
||||
log.Println("Created Chirpstack's API connection at: " + FindEnv("CHIRP_ADDRESS"))
|
||||
|
||||
// Test gRPC connection
|
||||
ctx_tmout, cancel_ctx = context.WithTimeout(context.Background(), Data.Timeout)
|
||||
defer cancel_ctx()
|
||||
|
||||
resp, err := Data.ChirpGateClient.List(ctx_tmout, &chirp_api.ListGatewaysRequest{
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
OrderByDesc: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Failed to test Chirpstack's API connection: "+err.Error(), resp)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Found the following gateways (first 10):")
|
||||
for idx, gate := range resp.Result {
|
||||
log.Println(strconv.FormatInt(int64(idx), 10) + ": App[" +
|
||||
gate.TenantId + "] GateID[" + gate.GatewayId + "] Name[" +
|
||||
gate.Name + "]")
|
||||
}
|
||||
log.Println("[END]")
|
||||
Data.printChirpStats()
|
||||
|
||||
// === Listen for Uplinks
|
||||
// Subscribe to topics
|
||||
topic := "application/" + FindEnv("APP_ID") + "/device/+/event/up"
|
||||
topic := "application/+/device/+/event/+"
|
||||
qos, err := strconv.Atoi(FindEnv("MQTT_QOS"))
|
||||
if err != nil {
|
||||
log.Println("Format misconfiguration for MQTT_QOS: " + err.Error())
|
||||
@@ -436,7 +463,35 @@ Expected: [number]{h|m|s|ms|us|ns}...`)
|
||||
client.Subscribe(topic, byte(qos), makeMQTTHandler(&Data))
|
||||
log.Println("Subscribed to uplink data on: " + topic + ".")
|
||||
|
||||
// === Simulate uplinks
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
uplink, err := MakePayload()
|
||||
if err != nil {
|
||||
log.Println("Error while creating uplink: " + err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(*uplink)
|
||||
if err != nil {
|
||||
log.Println("Failed to marshal uplink json: " + err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
client.Publish(
|
||||
"application/"+FindEnv("APP_ID")+"/device/"+DeviceEUI+"/event/up",
|
||||
0,
|
||||
false,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
log.Println("Stopping uplink loop...")
|
||||
}()
|
||||
|
||||
// === Shutdown
|
||||
<-Data.ExitSig // Continue here on force quit
|
||||
log.Println("The server is shutting down.")
|
||||
log.Println("The application is shutting down.")
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ const (
|
||||
NFC_LENGTH = 7
|
||||
)
|
||||
|
||||
var MAX_BUFFER_LENGTH = 221
|
||||
|
||||
var (
|
||||
ErrTooShort error = errors.New("insufficient bytes in message")
|
||||
ErrNoType error = errors.New("encountered unknown data type")
|
||||
@@ -76,6 +78,15 @@ type PagerMessage struct {
|
||||
Fields []Field
|
||||
}
|
||||
|
||||
// tryAppend is append function with custom contraints (like length)
|
||||
func tryAppend[T byte](slice []T, elems ...T) (bool, []T) {
|
||||
if len(slice)+len(elems) > MAX_BUFFER_LENGTH {
|
||||
return false, slice
|
||||
}
|
||||
|
||||
return true, append(slice, elems...)
|
||||
}
|
||||
|
||||
// readArray reads a subset of bytes marked with DT_Array.
|
||||
// This data is copied, it is not parsed.
|
||||
func readArray(data []byte, offset int, dest *[]byte) (int, error) {
|
||||
@@ -171,7 +182,10 @@ func readField(data []byte, offset int) (Field, int, error) {
|
||||
|
||||
// writeField encodes a field into buffer
|
||||
func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
buf = append(buf, field.DType)
|
||||
ok, buf := tryAppend(buf, field.DType)
|
||||
if !ok {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
switch field.DType {
|
||||
case DT_Array:
|
||||
@@ -179,7 +193,11 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
if !ok {
|
||||
return buf, ErrInvalidData
|
||||
}
|
||||
buf = append(buf, sub_pair.DType)
|
||||
|
||||
ok, buf = tryAppend(buf, sub_pair.DType)
|
||||
if !ok {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
size, ok := DataSizeMap[sub_pair.DType]
|
||||
if !ok {
|
||||
@@ -192,7 +210,7 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
length := len(val) / size
|
||||
if length > 255 {
|
||||
if length+len(buf)+1 > MAX_BUFFER_LENGTH {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
@@ -204,7 +222,10 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
return buf, ErrInvalidData
|
||||
}
|
||||
|
||||
buf = append(buf, val)
|
||||
ok, buf = tryAppend(buf, val)
|
||||
if !ok {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
case DT_Authorization, DT_NFCPair:
|
||||
val, ok := field.Value.([7]byte)
|
||||
@@ -212,7 +233,10 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
return buf, ErrInvalidData
|
||||
}
|
||||
|
||||
buf = append(buf, val[:]...)
|
||||
ok, buf = tryAppend(buf, val[:]...)
|
||||
if !ok {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
case DT_Charge, DT_Temperature:
|
||||
val, ok := field.Value.(float32)
|
||||
@@ -220,6 +244,9 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
return buf, ErrInvalidData
|
||||
}
|
||||
|
||||
if len(buf)+4 > MAX_BUFFER_LENGTH {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
buf = binary.LittleEndian.AppendUint32(buf, math.Float32bits(val))
|
||||
|
||||
case DT_Location:
|
||||
@@ -228,6 +255,10 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
return buf, ErrInvalidData
|
||||
}
|
||||
|
||||
if len(buf)+20 > MAX_BUFFER_LENGTH {
|
||||
return buf, ErrTooLong
|
||||
}
|
||||
|
||||
buf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(val.Latitude))
|
||||
buf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(val.Longitude))
|
||||
buf = binary.LittleEndian.AppendUint32(buf, math.Float32bits(val.Altitude))
|
||||
@@ -241,20 +272,27 @@ func writeField(field *Field, buf []byte) ([]byte, error) {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func GetLengthFromDataRate(data_rate int) int {
|
||||
if data_rate >= 0 && data_rate <= 3 {
|
||||
return 51
|
||||
} else if data_rate == 4 {
|
||||
return 115
|
||||
} else if data_rate >= 5 && data_rate <= 7 {
|
||||
return 222
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// SplitByLimit splits encoded message into parts
|
||||
// with maximum length derived from data rate.
|
||||
//
|
||||
// These messages are not decodable on the server, as
|
||||
// data is fractured. (TODO)
|
||||
func SplitByLimit(buf []byte, data_rate int) ([][]byte, error) {
|
||||
var limit int
|
||||
if data_rate >= 0 && data_rate <= 3 {
|
||||
limit = 51
|
||||
} else if data_rate == 4 {
|
||||
limit = 115
|
||||
} else if data_rate >= 5 && data_rate <= 7 {
|
||||
limit = 222
|
||||
} else {
|
||||
limit := GetLengthFromDataRate(data_rate)
|
||||
|
||||
if limit == 0 {
|
||||
return nil, ErrUndefined
|
||||
}
|
||||
|
||||
|
||||
136
AppServer/src/uplink.go
Normal file
136
AppServer/src/uplink.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"server/serder"
|
||||
)
|
||||
|
||||
// ---------- CONFIG ----------
|
||||
const (
|
||||
Broker = "tls://mosquitto:8883"
|
||||
GatewayID = "54d4d6102c916aed"
|
||||
DeviceEUI = "1122334455667788"
|
||||
AppId = "188c5d25-2d6b-43e3-aaf3-07dd0a8c386e"
|
||||
)
|
||||
|
||||
var FCount uint32 = 0
|
||||
|
||||
// ---------- DATA STRUCTURES ----------
|
||||
type Location struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Altitude float64 `json:"altitude"`
|
||||
}
|
||||
|
||||
type RXInfo struct {
|
||||
GatewayID string `json:"gatewayID"`
|
||||
Time time.Time `json:"time"`
|
||||
RSSI int `json:"rssi"`
|
||||
LoRaSNR float64 `json:"loRaSNR"`
|
||||
Channel int `json:"channel"`
|
||||
RFChain int `json:"rfChain"`
|
||||
Board int `json:"board"`
|
||||
Antenna int `json:"antenna"`
|
||||
Location Location `json:"location"`
|
||||
}
|
||||
|
||||
type TXInfo struct {
|
||||
Frequency int `json:"frequency"`
|
||||
DR int `json:"dr"`
|
||||
ADR bool `json:"adr"`
|
||||
CodeRate string `json:"codeRate"`
|
||||
}
|
||||
|
||||
type Uplink struct {
|
||||
ApplicationID string `json:"applicationID"`
|
||||
ApplicationName string `json:"applicationName"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DevEUI string `json:"devEUI"`
|
||||
RXInfo []RXInfo `json:"rxInfo"`
|
||||
TXInfo TXInfo `json:"txInfo"`
|
||||
FCnt uint32 `json:"fCnt"`
|
||||
FPort uint8 `json:"fPort"`
|
||||
Data string `json:"data"` // Base64-encoded payload
|
||||
Object any `json:"object"` // decoded JSON payload if device uses JSON codec
|
||||
}
|
||||
|
||||
// ---------- UTILITY FUNCTIONS ----------
|
||||
// func generateRandomPHY(n int) []byte {
|
||||
// payload := make([]byte, n)
|
||||
// for i := 0; i < n; i++ {
|
||||
// payload[i] = byte(rand.Intn(256))
|
||||
// }
|
||||
// return payload
|
||||
// }
|
||||
|
||||
// Create a random uplink
|
||||
func MakePayload() (*Uplink, error) {
|
||||
|
||||
// Generate random sensor data
|
||||
custom_pay := serder.PagerMessage{
|
||||
AppID: 1,
|
||||
Fields: []serder.Field{
|
||||
{
|
||||
DType: serder.DT_Array,
|
||||
Value: serder.Field{
|
||||
DType: serder.DT_Char,
|
||||
Value: []byte{
|
||||
byte(5),
|
||||
'H', 'e', 'l', 'l', 'o',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
object, err := serder.Serialize(&custom_pay)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode object as JSON and then as base64 for Data field
|
||||
objBytes, _ := json.Marshal(object)
|
||||
dataB64 := base64.StdEncoding.EncodeToString(objBytes)
|
||||
|
||||
uplink := Uplink{
|
||||
ApplicationID: AppId,
|
||||
ApplicationName: "pagerino-app",
|
||||
DeviceName: "pager-1",
|
||||
DevEUI: DeviceEUI,
|
||||
RXInfo: []RXInfo{
|
||||
{
|
||||
GatewayID: GatewayID,
|
||||
Time: time.Now().UTC(),
|
||||
RSSI: -30 + rand.Intn(10),
|
||||
LoRaSNR: -5 + rand.Float64()*10,
|
||||
Channel: 0,
|
||||
RFChain: 0,
|
||||
Board: 1,
|
||||
Antenna: 0,
|
||||
Location: Location{
|
||||
Latitude: 52.0 + rand.Float64(),
|
||||
Longitude: 13.0 + rand.Float64(),
|
||||
Altitude: 100 + rand.Float64()*50,
|
||||
},
|
||||
},
|
||||
},
|
||||
TXInfo: TXInfo{
|
||||
Frequency: 868100000,
|
||||
DR: 5,
|
||||
ADR: true,
|
||||
CodeRate: "4/5",
|
||||
},
|
||||
FCnt: FCount,
|
||||
FPort: 1,
|
||||
Data: dataB64,
|
||||
Object: object,
|
||||
}
|
||||
|
||||
FCount++
|
||||
|
||||
return &uplink, err
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user