mirror of
https://codeberg.org/portospaceteam/ground-dashboard.git
synced 2024-11-28 17:56:59 +00:00
Receive data in dashboard
This commit is contained in:
parent
a9214847c4
commit
12f414b166
@ -5,19 +5,7 @@ import (
|
||||
)
|
||||
|
||||
func basePressure(stream FlightData) float64 {
|
||||
pressures := make([]float64, 0)
|
||||
for _, segment := range stream.AllSegments() {
|
||||
if segment.Computed.SmoothedPressure > 0 {
|
||||
pressures = append(pressures, segment.Computed.SmoothedPressure)
|
||||
}
|
||||
if len(pressures) >= 10 {
|
||||
var sum float64 = 0
|
||||
for _, v := range pressures {
|
||||
sum += v
|
||||
}
|
||||
return nanSafe(sum / float64(len(pressures)))
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -25,38 +13,31 @@ func altitude(bp float64, raw RawDataSegment) float64 {
|
||||
if bp == 0 {
|
||||
return 0
|
||||
}
|
||||
return nanSafe(44307.7 * (1 - math.Pow((raw.Pressure/100)/bp, 0.190284)))
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func normalizedPressure(raw RawDataSegment) float64 {
|
||||
return nanSafe(raw.Pressure / 100.0)
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func velocity(stream FlightData, bp float64, raw RawDataSegment) float64 {
|
||||
altitude := altitude(bp, raw)
|
||||
segments := stream.AllSegments()
|
||||
for i := len(segments) - 1; i >= 0; i -= 1 {
|
||||
if segments[i].Computed.Altitude != altitude {
|
||||
return nanSafe((altitude - segments[i].Computed.Altitude) / (raw.Timestamp - segments[i].Raw.Timestamp))
|
||||
}
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func yaw(raw RawDataSegment) float64 {
|
||||
return nanSafe(math.Atan2(-1.0*raw.Acceleration.X, raw.Acceleration.Z) * (180.0 / math.Pi))
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func pitch(raw RawDataSegment) float64 {
|
||||
return nanSafe(math.Atan2(-1.0*raw.Acceleration.Y, raw.Acceleration.Z) * (180.0 / math.Pi))
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func toRadians(degrees float64) float64 {
|
||||
return nanSafe(degrees * math.Pi / 180)
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func toDegrees(radians float64) float64 {
|
||||
return nanSafe(radians * 180 / math.Pi)
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func bearing(origin Coordinate, raw RawDataSegment) float64 {
|
||||
@ -77,121 +58,60 @@ func bearing(origin Coordinate, raw RawDataSegment) float64 {
|
||||
}
|
||||
|
||||
func distance(origin Coordinate, raw RawDataSegment) float64 {
|
||||
if origin.Lat == 0 || origin.Lon == 0 || raw.Coordinate.Lat == 0 || raw.Coordinate.Lon == 0 {
|
||||
return 0
|
||||
}
|
||||
R := 6371e3
|
||||
φ1 := origin.Lat * math.Pi / 180
|
||||
φ2 := raw.Coordinate.Lat * math.Pi / 180
|
||||
Δφ := (raw.Coordinate.Lat - origin.Lat) * math.Pi / 180
|
||||
Δλ := (raw.Coordinate.Lon - origin.Lon) * math.Pi / 180
|
||||
a := math.Sin(Δφ/2)*math.Sin(Δφ/2) + math.Cos(φ1)*math.Cos(φ2)*math.Sin(Δλ/2)*math.Sin(Δλ/2)
|
||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||
return nanSafe(R * c)
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func dataRate(stream FlightData) float64 {
|
||||
totalsMap := make(map[int]float64)
|
||||
for _, timestamp := range stream.Time() {
|
||||
second := int(math.Floor(timestamp))
|
||||
if total, ok := totalsMap[second]; ok {
|
||||
totalsMap[second] = total + 1
|
||||
} else {
|
||||
totalsMap[second] = 1
|
||||
}
|
||||
}
|
||||
total := 0.0
|
||||
for _, secondTotal := range totalsMap {
|
||||
total += secondTotal
|
||||
}
|
||||
return nanSafe(total / float64(len(totalsMap)))
|
||||
|
||||
return nanSafe(0.0)
|
||||
}
|
||||
|
||||
func averageComputedValue(seconds float64, stream FlightData, raw RawDataSegment, computed ComputedDataSegment, accessor func(seg ComputedDataSegment) float64) float64 {
|
||||
total := accessor(computed)
|
||||
n := 1.0
|
||||
i := len(stream.AllSegments()) - 1
|
||||
for i >= 0 && raw.Timestamp-stream.Time()[i] <= seconds {
|
||||
total += accessor(stream.AllSegments()[i].Computed)
|
||||
n++
|
||||
i--
|
||||
}
|
||||
return nanSafe(total / n)
|
||||
return nanSafe(total)
|
||||
}
|
||||
|
||||
func determineFlightMode(stream FlightData, raw RawDataSegment, computed ComputedDataSegment) FlightMode {
|
||||
length := len(stream.AllSegments())
|
||||
if length == 0 {
|
||||
return ModePrelaunch
|
||||
switch int(raw.FlightPhase) {
|
||||
case 1:
|
||||
return ModeWaitingLaunch
|
||||
case 2:
|
||||
return ModeLaunch
|
||||
case 4:
|
||||
return ModeLowVelocity
|
||||
case 5:
|
||||
return ModeNoseOver
|
||||
case 6:
|
||||
return ModeDrogueFired
|
||||
case 7:
|
||||
return ModeMainFired
|
||||
case 8:
|
||||
return ModeFailsafe
|
||||
case 9:
|
||||
return ModeLanding
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
lastMode := stream.AllSegments()[length-1].Computed.FlightMode
|
||||
avgVelocity := averageComputedValue(1, stream, raw, computed, func(seg ComputedDataSegment) float64 {
|
||||
return seg.SmoothedVelocity
|
||||
})
|
||||
avgAcceleration := averageComputedValue(1, stream, raw, computed, func(seg ComputedDataSegment) float64 {
|
||||
return seg.SmoothedVerticalAcceleration
|
||||
})
|
||||
if lastMode == ModePrelaunch && avgVelocity > 5 {
|
||||
return ModeAscentPowered
|
||||
}
|
||||
if lastMode == ModeAscentPowered && avgAcceleration < 0 && avgVelocity > 0 {
|
||||
return ModeAscentUnpowered
|
||||
}
|
||||
if (lastMode == ModeAscentPowered || lastMode == ModeAscentUnpowered) && avgVelocity < 0 {
|
||||
return ModeDescentFreefall
|
||||
}
|
||||
if lastMode == ModeDescentFreefall && math.Abs(avgAcceleration) < 0.5 {
|
||||
return ModeDescentParachute
|
||||
}
|
||||
if (lastMode == ModeDescentFreefall || lastMode == ModeDescentParachute) && math.Abs(avgAcceleration) < 0.5 && math.Abs(avgVelocity) < 0.5 {
|
||||
return ModeRecovery
|
||||
}
|
||||
return lastMode
|
||||
}
|
||||
|
||||
func ComputeDataSegment(stream FlightData, raw RawDataSegment) (ComputedDataSegment, float64, Coordinate) {
|
||||
bp := stream.BasePressure()
|
||||
if bp == 0 {
|
||||
bp = basePressure(stream)
|
||||
}
|
||||
bp := 0.0
|
||||
|
||||
origin := stream.Origin()
|
||||
if origin.Lat == 0 && origin.Lon == 0 && raw.Coordinate.Lat != 0 && raw.Coordinate.Lon != 0 {
|
||||
origin = raw.Coordinate
|
||||
}
|
||||
|
||||
alt := altitude(bp, raw)
|
||||
vel := velocity(stream, bp, raw)
|
||||
press := normalizedPressure(raw)
|
||||
|
||||
smoothedAlt := alt
|
||||
smoothedVel := vel
|
||||
smoothedVertAccel := 0.0
|
||||
smoothedPress := press
|
||||
smoothedTemp := raw.Temperature
|
||||
s := len(stream.AllSegments())
|
||||
if s > 0 {
|
||||
alpha := 0.5
|
||||
smoothedAlt = smoothed(alpha, alt, stream.SmoothedAltitude()[s-1])
|
||||
smoothedVel = smoothed(alpha, vel, stream.SmoothedVelocity()[s-1])
|
||||
smoothedPress = smoothed(alpha, press, stream.SmoothedPressure()[s-1])
|
||||
smoothedTemp = smoothed(alpha, raw.Temperature, stream.SmoothedTemperature()[s-1])
|
||||
smoothedVertAccel = (smoothedVel - stream.SmoothedVelocity()[s-1]) / (raw.Timestamp - stream.Time()[s-1])
|
||||
}
|
||||
origin := raw.Coordinate
|
||||
|
||||
computed := ComputedDataSegment{
|
||||
Altitude: alt,
|
||||
Velocity: vel,
|
||||
Yaw: yaw(raw),
|
||||
Pitch: pitch(raw),
|
||||
Bearing: bearing(origin, raw),
|
||||
Distance: distance(origin, raw),
|
||||
Altitude: raw.Altitude,
|
||||
Velocity: raw.Velocity,
|
||||
Yaw: 0,
|
||||
Pitch: 0,
|
||||
Bearing: 0,
|
||||
Distance: 0,
|
||||
DataRate: dataRate(stream),
|
||||
SmoothedAltitude: smoothedAlt,
|
||||
SmoothedVelocity: smoothedVel,
|
||||
SmoothedPressure: smoothedPress,
|
||||
SmoothedTemperature: smoothedTemp,
|
||||
SmoothedVerticalAcceleration: smoothedVertAccel,
|
||||
SmoothedAltitude: raw.Altitude,
|
||||
SmoothedVelocity: raw.Velocity,
|
||||
SmoothedPressure: 0.0,
|
||||
SmoothedTemperature: raw.Temperature,
|
||||
SmoothedVerticalAcceleration: 0.0,
|
||||
}
|
||||
|
||||
computed.FlightMode = determineFlightMode(stream, raw, computed)
|
||||
|
@ -1,176 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBasePressureSet(t *testing.T) {
|
||||
segments, avg := makeDataSeries(0)
|
||||
b := basePressure(&FlightDataConcrete{
|
||||
Base: 0,
|
||||
Segments: segments,
|
||||
OriginCoordinate: Coordinate{},
|
||||
})
|
||||
assert.Equal(t, b, avg)
|
||||
}
|
||||
|
||||
func TestAltitudeNoBase(t *testing.T) {
|
||||
alt := altitude(0, RawDataSegment{
|
||||
Pressure: rand.Float64(),
|
||||
})
|
||||
assert.Equal(t, alt, 0.0)
|
||||
}
|
||||
|
||||
func TestAltitudeBase(t *testing.T) {
|
||||
alt := altitude(1012, RawDataSegment{
|
||||
Pressure: 1010,
|
||||
})
|
||||
assert.Equal(t, alt, 25868.260058108597)
|
||||
}
|
||||
|
||||
func TestNormalizedPressure(t *testing.T) {
|
||||
p := rand.Float64() * 1000
|
||||
v := normalizedPressure(RawDataSegment{Pressure: p})
|
||||
assert.Equal(t, v, p/100)
|
||||
}
|
||||
|
||||
func TestVelocity(t *testing.T) {
|
||||
bp := 1012.0
|
||||
segments, _ := makeDataSeries(bp)
|
||||
val := (rand.Float64()*20 + 1000) * 100.0
|
||||
seg := RawDataSegment{
|
||||
Timestamp: float64(len(segments)),
|
||||
Pressure: val,
|
||||
}
|
||||
vel := velocity(&FlightDataConcrete{
|
||||
Base: 0,
|
||||
Segments: segments,
|
||||
OriginCoordinate: Coordinate{},
|
||||
}, bp, seg)
|
||||
vel1 := (altitude(bp, seg) - segments[len(segments)-1].Computed.Altitude) / (seg.Timestamp - segments[len(segments)-1].Raw.Timestamp)
|
||||
assert.Equal(t, vel, vel1)
|
||||
}
|
||||
|
||||
func TestYaw(t *testing.T) {
|
||||
val := yaw(RawDataSegment{
|
||||
Acceleration: XYZ{
|
||||
X: 100,
|
||||
Y: 110,
|
||||
Z: 120,
|
||||
},
|
||||
})
|
||||
assert.Equal(t, val, -39.80557109226519)
|
||||
}
|
||||
|
||||
func TestPitch(t *testing.T) {
|
||||
val := pitch(RawDataSegment{
|
||||
Acceleration: XYZ{
|
||||
X: 100,
|
||||
Y: 110,
|
||||
Z: 120,
|
||||
},
|
||||
})
|
||||
assert.Equal(t, val, -42.51044707800084)
|
||||
}
|
||||
|
||||
func TestToDegrees(t *testing.T) {
|
||||
val := toDegrees(math.Pi)
|
||||
assert.Equal(t, val, 180.0)
|
||||
}
|
||||
|
||||
func TestToRadians(t *testing.T) {
|
||||
val := toRadians(90)
|
||||
assert.Equal(t, val, math.Pi/2)
|
||||
}
|
||||
|
||||
func TestBearing(t *testing.T) {
|
||||
origin := Coordinate{
|
||||
38.811423646113546,
|
||||
-77.054951464077,
|
||||
}
|
||||
seg := RawDataSegment{
|
||||
Coordinate: Coordinate{
|
||||
38,
|
||||
-77,
|
||||
},
|
||||
}
|
||||
b := bearing(origin, seg)
|
||||
assert.Equal(t, b, 179.9862686631269)
|
||||
}
|
||||
|
||||
func TestDistance(t *testing.T) {
|
||||
origin := Coordinate{
|
||||
38.811423646113546,
|
||||
-77.054951464077,
|
||||
}
|
||||
seg := RawDataSegment{
|
||||
Coordinate: Coordinate{
|
||||
38,
|
||||
-77,
|
||||
},
|
||||
}
|
||||
b := distance(origin, seg)
|
||||
assert.Equal(t, b, 90353.15173806295)
|
||||
}
|
||||
|
||||
func TestDataRate(t *testing.T) {
|
||||
segments, _ := makeDataSeries(0)
|
||||
rate := dataRate(&FlightDataConcrete{
|
||||
Segments: segments,
|
||||
})
|
||||
assert.Equal(t, rate, 1.0)
|
||||
}
|
||||
|
||||
func TestComputeDataSegment(t *testing.T) {
|
||||
segments, avg := makeDataSeries(0)
|
||||
segment, bp, origin := ComputeDataSegment(&FlightDataConcrete{
|
||||
Segments: segments,
|
||||
OriginCoordinate: Coordinate{37, -76},
|
||||
}, RawDataSegment{
|
||||
WriteProgress: 1.0,
|
||||
Timestamp: float64(len(segments) + 1),
|
||||
Pressure: 1014.0,
|
||||
Temperature: 30.0,
|
||||
Acceleration: XYZ{1, 2, 3},
|
||||
Magnetic: XYZ{1, 2, 3},
|
||||
Coordinate: Coordinate{38, -77},
|
||||
GPSInfo: GPSInfo{0.0, 0.0},
|
||||
Rssi: 0,
|
||||
})
|
||||
assert.Equal(t, bp, avg)
|
||||
assert.NotEqual(t, origin.Lat, 0.0)
|
||||
assert.NotEqual(t, origin.Lon, 0.0)
|
||||
assert.NotEqual(t, segment.Altitude, 0.0)
|
||||
assert.NotEqual(t, segment.Velocity, 0.0)
|
||||
assert.NotEqual(t, segment.Yaw, 0.0)
|
||||
assert.NotEqual(t, segment.Pitch, 0.0)
|
||||
assert.NotEqual(t, segment.Bearing, 0.0)
|
||||
assert.NotEqual(t, segment.Distance, 0.0)
|
||||
assert.NotEqual(t, segment.DataRate, 0.0)
|
||||
}
|
||||
|
||||
func makeDataSeries(bp float64) ([]DataSegment, float64) {
|
||||
series := make([]DataSegment, 10)
|
||||
total := 0.0
|
||||
for i := 0; i < len(series); i++ {
|
||||
val := rand.Float64()*20 + 1000
|
||||
total += val
|
||||
series[i] = DataSegment{
|
||||
RawDataSegment{
|
||||
Timestamp: float64(i),
|
||||
Pressure: val * 100.0,
|
||||
},
|
||||
ComputedDataSegment{
|
||||
Altitude: altitude(bp, RawDataSegment{
|
||||
Pressure: val * 100.0,
|
||||
}),
|
||||
SmoothedPressure: val,
|
||||
},
|
||||
}
|
||||
}
|
||||
return series, total / float64(len(series))
|
||||
}
|
@ -1,26 +1,25 @@
|
||||
package core
|
||||
|
||||
const (
|
||||
ModePrelaunch = "P"
|
||||
ModeAscentPowered = "AP"
|
||||
ModeAscentUnpowered = "AU"
|
||||
ModeDescentFreefall = "DF"
|
||||
ModeDescentParachute = "DP"
|
||||
ModeRecovery = "R"
|
||||
ModeWaitingLaunch = "WT"
|
||||
ModeLaunch = "LD"
|
||||
ModeLowVelocity = "LV"
|
||||
ModeNoseOver = "AP"
|
||||
ModeDrogueFired = "DR"
|
||||
ModeMainFired = "MN"
|
||||
ModeFailsafe = "FS"
|
||||
ModeLanding = "TD"
|
||||
)
|
||||
|
||||
const (
|
||||
IndexTimestamp = 0
|
||||
IndexPressure = 1
|
||||
IndexTemperature = 2
|
||||
IndexAccelerationX = 3
|
||||
IndexAccelerationY = 4
|
||||
IndexAccelerationZ = 5
|
||||
IndexMagneticX = 6
|
||||
IndexMagneticY = 7
|
||||
IndexMagneticZ = 8
|
||||
IndexCoordinateLat = 9
|
||||
IndexCoordinateLon = 10
|
||||
IndexGpsQuality = 11
|
||||
IndexGpsSats = 12
|
||||
IndexFlightTime = 0
|
||||
IndexFightPhase = 1
|
||||
IndexAltitude = 2
|
||||
IndexVelocity = 3
|
||||
IndexAcceleration = 4
|
||||
IndexTemperature = 5
|
||||
IndexCoordinateLat = 6
|
||||
IndexCoordinateLon = 7
|
||||
IndexGpsQuality = 8
|
||||
IndexGpsSats = 9
|
||||
)
|
||||
|
@ -62,7 +62,7 @@ func (f *FlightDataConcrete) GpsSats() []float64 {
|
||||
|
||||
func (f *FlightDataConcrete) Time() []float64 {
|
||||
return singleFlightDataElement(f, func(segment DataSegment) float64 {
|
||||
return segment.Raw.Timestamp
|
||||
return 0.0
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@ type XYZ struct {
|
||||
}
|
||||
|
||||
type RawDataSegment struct {
|
||||
WriteProgress float64 `json:"writeProgress"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
Pressure float64 `json:"pressure"`
|
||||
FlightTime float64 `json:"flightTime"`
|
||||
FlightPhase float64 `json:"flightPhase"`
|
||||
Altitude float64 `json:"altitude"`
|
||||
Velocity float64 `json:"velocity"`
|
||||
Acceleration float64 `json:"accelereation"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Acceleration XYZ `json:"acceleration"`
|
||||
Magnetic XYZ `json:"magnetic"`
|
||||
Coordinate Coordinate `json:"coordinate"`
|
||||
GPSInfo GPSInfo `json:"gpsInfo"`
|
||||
Rssi int16 `json:"rssi"`
|
||||
|
@ -1,5 +1,5 @@
|
||||
package main
|
||||
|
||||
const (
|
||||
PointsPerDataFrame = 2
|
||||
PointsPerDataFrame = 1
|
||||
)
|
||||
|
@ -37,18 +37,15 @@ func StartTextLogger(p DataProvider, ds core.FlightData, logger LoggerControl) e
|
||||
|
||||
headers := []string{
|
||||
"Time",
|
||||
"Prog",
|
||||
"Pressure",
|
||||
"Temp",
|
||||
"Accel X",
|
||||
"Accel Y",
|
||||
"Accel Z",
|
||||
"Mag X",
|
||||
"Mag Y",
|
||||
"Mag Z",
|
||||
"Lat",
|
||||
"Lon",
|
||||
"Sats",
|
||||
"Phase",
|
||||
"Altitude",
|
||||
"Velocity",
|
||||
"Acceleration",
|
||||
"Temperature",
|
||||
"Latitude",
|
||||
"longitude",
|
||||
"GPS Quality",
|
||||
"GPS Sats",
|
||||
"Qual",
|
||||
"RSSI",
|
||||
}
|
||||
@ -99,16 +96,12 @@ func StartTextLogger(p DataProvider, ds core.FlightData, logger LoggerControl) e
|
||||
j := len(data) - nRows + 1 + i
|
||||
seg := data[j]
|
||||
rows[i+1] = []string{
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Timestamp),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.WriteProgress),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Pressure),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.FlightTime),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.FlightPhase),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Altitude),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Velocity),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Acceleration),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Temperature),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Acceleration.X),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Acceleration.Y),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Acceleration.Z),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Magnetic.X),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Magnetic.Y),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Magnetic.Z),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Coordinate.Lat),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.Coordinate.Lon),
|
||||
fmt.Sprintf("%0.2f", seg.Raw.GPSInfo.Sats),
|
||||
@ -229,5 +222,6 @@ func StartWeb(p DataProvider, ds core.FlightData, logger LoggerControl) error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
const webSocket = new WebSocket(`ws://${window.location.host}/api/data`)
|
||||
webSocket.onmessage = (e) => {
|
||||
data = data.concat(JSON.parse(e.data))
|
||||
console.log( "DATA :" + data)
|
||||
dashboard.update(data)
|
||||
}
|
||||
})()
|
||||
|
@ -1,10 +1,12 @@
|
||||
const modeMap = {
|
||||
'P': 'Prelaunch',
|
||||
'AP': 'Powered Ascent',
|
||||
'AU': 'Unpowered Ascent',
|
||||
'DF': 'Freefall Descent',
|
||||
'DP': 'Parachute Descent',
|
||||
'R': 'Recovery'
|
||||
"WT": "Waiting for Launch",
|
||||
"LD": "Launch Detected",
|
||||
"LV": "Low velocity Detected",
|
||||
"AP": "Nose-Over",
|
||||
"DR": "Drogue Fired",
|
||||
"MN": "Main Fired",
|
||||
"FS": "Failsafe Triggered ",
|
||||
"TD": "Landing Detected"
|
||||
}
|
||||
|
||||
class MissionInfoWidget extends Widget {
|
||||
|
@ -1,6 +1,6 @@
|
||||
function makeXYExtractor(propType, propName) {
|
||||
return (data) => data.map(segment => ({
|
||||
x: segment.raw.timestamp,
|
||||
x: segment.raw.flightTime,
|
||||
y: segment[propType][propName]
|
||||
}))
|
||||
}
|
||||
@ -15,8 +15,7 @@ function makeCoordinateExtractor() {
|
||||
|
||||
function makeInfoExtractor() {
|
||||
return (data) => ({
|
||||
pcnt: data[data.length - 1].raw.cameraProgress,
|
||||
time: data[data.length - 1].raw.timestamp,
|
||||
time: data[data.length - 1].raw.flightTime,
|
||||
mode: data[data.length - 1].computed.flightMode
|
||||
})
|
||||
}
|
||||
|
@ -33,11 +33,13 @@ func telemetryIntFromBytes(b []byte) int16 {
|
||||
}
|
||||
|
||||
func decodeTelemetryBytes(bytes []byte) ([]byte, []byte, error) {
|
||||
|
||||
parts := strings.Split(string(bytes), ",")
|
||||
if len(parts) != 3 || parts[0] != "T" {
|
||||
return nil, nil, errors.New("bad telemetry")
|
||||
return nil, nil, errors.New(string(bytes))
|
||||
}
|
||||
telemetryBytes, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -58,22 +60,15 @@ func bytesToDataSegment(stream core.FlightData, bytes []byte) ([]core.DataSegmen
|
||||
var basePressure float64
|
||||
var origin core.Coordinate
|
||||
for i := len(segments) - 1; i >= 0; i-- {
|
||||
offset := 1 + (i * 13)
|
||||
offset := (i * 10)
|
||||
raw := core.RawDataSegment{
|
||||
WriteProgress: telemetryFloatFromByteIndex(telemetryBytes, 0),
|
||||
Timestamp: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexTimestamp),
|
||||
Pressure: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexPressure),
|
||||
FlightTime: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexFlightTime),
|
||||
FlightPhase: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexFightPhase),
|
||||
Altitude: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexAltitude),
|
||||
Velocity: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexVelocity),
|
||||
Acceleration: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexAcceleration),
|
||||
Temperature: telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexTemperature),
|
||||
Acceleration: core.XYZ{
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexAccelerationX),
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexAccelerationY),
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexAccelerationZ),
|
||||
},
|
||||
Magnetic: core.XYZ{
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexMagneticX),
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexMagneticY),
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexMagneticZ),
|
||||
},
|
||||
|
||||
Coordinate: core.Coordinate{
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexCoordinateLat),
|
||||
telemetryFloatFromByteIndex(telemetryBytes, offset+core.IndexCoordinateLon),
|
||||
|
@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTelemetryFloatFromByteIndex(t *testing.T) {
|
||||
b64 := "AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAhA"
|
||||
data, _ := base64.StdEncoding.DecodeString(b64)
|
||||
n := telemetryFloatFromByteIndex(data, 1)
|
||||
assert.Equal(t, n, 2.0)
|
||||
}
|
||||
|
||||
func TestTelemetryIntFromBytes(t *testing.T) {
|
||||
n := telemetryIntFromBytes([]byte{0x01, 0x00})
|
||||
assert.Equal(t, n, int16(1))
|
||||
}
|
||||
|
||||
func TestDecodeTelemetryBytes(t *testing.T) {
|
||||
telemetryBytes, rssiBytes, err := decodeTelemetryBytes([]byte("T,wcqhRbbz3T8NAIDUU3JoQGzPymub5/dAAQAM3C/rIkCoKRPINnrovxg/EbSXB+Y/qdDM1YdeMMD+XHTRRRctQAAAAAAAgELAT6wPjfWhL0D2QZYFE2dDQCC4yhMIQ1PAAAAAAAAAAAAAAAAAAAAAAA==,//8="))
|
||||
assert.NotNil(t, telemetryBytes)
|
||||
assert.NotNil(t, rssiBytes)
|
||||
assert.Nil(t, err)
|
||||
}
|
@ -21,7 +21,7 @@ func (c AltitudeChart) Generate(offsetSeconds float64, fd []core.DataSegment) er
|
||||
Series: []chart.Series{
|
||||
chart.ContinuousSeries{
|
||||
Name: "Altitude",
|
||||
XValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Raw.Timestamp - offsetSeconds }),
|
||||
XValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Raw.flightTime - offsetSeconds }),
|
||||
YValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Computed.SmoothedAltitude }),
|
||||
},
|
||||
},
|
||||
|
@ -21,7 +21,7 @@ func (c VelocityChart) Generate(offsetSeconds float64, fd []core.DataSegment) er
|
||||
Series: []chart.Series{
|
||||
chart.ContinuousSeries{
|
||||
Name: "Velocity",
|
||||
XValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Raw.Timestamp - offsetSeconds }),
|
||||
XValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Raw.flightTime - offsetSeconds }),
|
||||
YValues: singleFlightDataElement(fd, func(d core.DataSegment) float64 { return d.Computed.SmoothedVelocity }),
|
||||
},
|
||||
},
|
||||
|
@ -6,9 +6,9 @@ func timeInMode(fd []core.DataSegment, mode core.FlightMode) float64 {
|
||||
startTime := -1.0
|
||||
for _, d := range fd {
|
||||
if d.Computed.FlightMode == mode && startTime < 0 {
|
||||
startTime = d.Raw.Timestamp
|
||||
startTime = d.Raw.flightTime
|
||||
} else if d.Computed.FlightMode != mode && startTime > 0 {
|
||||
return d.Raw.Timestamp - startTime
|
||||
return d.Raw.flightTime - startTime
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
@ -24,8 +24,8 @@ func flightDataFromFile(input string) ([]core.DataSegment, error) {
|
||||
|
||||
func determineOffsetSeconds(ds []core.DataSegment) float64 {
|
||||
for _, d := range ds {
|
||||
if d.Computed.FlightMode == core.ModeAscentPowered {
|
||||
return d.Raw.Timestamp
|
||||
if d.Computed.FlightMode == core.ModeLaunch {
|
||||
return d.Raw.flightTime
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
@ -1,6 +1,26 @@
|
||||
#include <SPI.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <base64.hpp>
|
||||
|
||||
#define BIT(n) (1 << (n))
|
||||
|
||||
#define BIT_FLIGHT_TIME BIT(0)
|
||||
#define BIT_ALTITUDE_100 BIT(1)
|
||||
#define BIT_ALTITUDE BIT(2)
|
||||
#define BIT_VELOCITY BIT(3)
|
||||
#define BIT_ACCELERATION BIT(4)
|
||||
#define BIT_FLIGHT_PHASE BIT(5)
|
||||
#define BIT_CHANNEL BIT(6)
|
||||
#define BIT_TEMPERATURE_10 BIT(7)
|
||||
#define BIT_NAME BIT(8)
|
||||
#define BIT_BATERY_10 BIT(9)
|
||||
#define BIT_APOGEE BIT(10)
|
||||
#define BIT_MAX_VELOCITY BIT(11)
|
||||
#define BIT_MAX_ACCELERATION BIT(12)
|
||||
#define ALL_ELEMENTS 0b1111111111111
|
||||
|
||||
#define TRIGGER_FLIGHT_TIME '#'
|
||||
#define TRIGGER_ALTITUDE_100 '{'
|
||||
@ -17,60 +37,25 @@
|
||||
#define TRIGGER_MAX_ACCELERATION '['
|
||||
#define TRIGGER_END_PACKET '>'
|
||||
|
||||
typedef union EggtimerData
|
||||
{
|
||||
int fight_time;
|
||||
int altitude_100;
|
||||
int altitude;
|
||||
int velocity;
|
||||
int acceleration_10;
|
||||
int fight_phase;
|
||||
char channel[6];
|
||||
int temperature_10;
|
||||
char name[9];
|
||||
int battery_voltage_10;
|
||||
int apogee;
|
||||
int max_velocity;
|
||||
int max_acceleration;
|
||||
}EggtimerData;
|
||||
#define MAX_DATA_SIZE 100
|
||||
|
||||
typedef struct EggtimerElementPacket{
|
||||
typedef struct WhiteVestsPacket{
|
||||
double flight_time;
|
||||
double flight_phase;
|
||||
double altitude;
|
||||
double velocity;
|
||||
double acceleration;
|
||||
double temperature;
|
||||
double latitude;
|
||||
double longitude;
|
||||
double gpsSignal; // GPS Signal Strength
|
||||
double gpsstat; // GPS Status
|
||||
}WhiteVestsPacket;
|
||||
|
||||
typedef struct EggtimerElement{
|
||||
char type;
|
||||
EggtimerData data;
|
||||
} EggtimerElementPacket;
|
||||
|
||||
static char _last_state = 0;
|
||||
static size_t counter = 0;
|
||||
|
||||
/**
|
||||
* @brief this function stores the data in the element packet
|
||||
*/
|
||||
void _save_data(char byte_received, EggtimerElementPacket *element){
|
||||
switch (_last_state)
|
||||
{
|
||||
case TRIGGER_FLIGHT_TIME:
|
||||
case TRIGGER_ALTITUDE_100:
|
||||
case TRIGGER_ALTITUDE:
|
||||
case TRIGGER_VELOCITY:
|
||||
case TRIGGER_ACCELERATION_10:
|
||||
case TRIGGER_FLIGHT_PHASE:
|
||||
case TRIGGER_TEMPERATURE_10:
|
||||
case TRIGGER_BATERY_10:
|
||||
case TRIGGER_APOGEE:
|
||||
case TRIGGER_MAX_VELOCITY:
|
||||
case TRIGGER_MAX_ACCELERATION:
|
||||
element->data.altitude *= 10;
|
||||
element->data.altitude += byte_received - '0';
|
||||
break;
|
||||
case TRIGGER_CHANNEL:
|
||||
case TRIGGER_NAME:
|
||||
element->data.name[counter++] = byte_received;
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
char data[MAX_DATA_SIZE];
|
||||
} EggtimerElement;
|
||||
|
||||
/**
|
||||
* @brief This function is used to parse the data received from the Eggtimer
|
||||
@ -79,7 +64,9 @@ void _save_data(char byte_received, EggtimerElementPacket *element){
|
||||
*
|
||||
* @return true if receive new data packet, false otherwise
|
||||
*/
|
||||
bool decode_eggtimer_data(char byte_received, EggtimerElementPacket * packet){
|
||||
bool decode_eggtimer_data(char byte_received, EggtimerElement * packet){
|
||||
static size_t counter = 0;
|
||||
static char last_state = 0;
|
||||
|
||||
switch (byte_received)
|
||||
{
|
||||
@ -97,59 +84,242 @@ bool decode_eggtimer_data(char byte_received, EggtimerElementPacket * packet){
|
||||
case TRIGGER_MAX_VELOCITY:
|
||||
case TRIGGER_MAX_ACCELERATION:
|
||||
|
||||
_last_state = byte_received;
|
||||
last_state = byte_received;
|
||||
packet->type = byte_received;
|
||||
|
||||
memset(&packet->data, 0, sizeof packet->data);
|
||||
Serial.println( byte_received);
|
||||
counter = 0;
|
||||
break;
|
||||
|
||||
case TRIGGER_END_PACKET:
|
||||
if (_last_state){
|
||||
packet->type = _last_state;
|
||||
|
||||
_last_state = 0;
|
||||
if (last_state){
|
||||
packet->type = last_state;
|
||||
packet->data[counter] = '\0';
|
||||
last_state = 0;
|
||||
return true;
|
||||
}
|
||||
_last_state = 0;
|
||||
last_state = 0;
|
||||
break;
|
||||
default:
|
||||
_save_data(byte_received, packet);
|
||||
// save data
|
||||
if(last_state)
|
||||
packet->data[counter++] = byte_received;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool test(){
|
||||
char str[] = "{004>@5>#0132>~1B---->(00023>\\000>%04679>^0660>[025>?079>!201>=KM6ZFL>";
|
||||
char *p = str;
|
||||
EggtimerElementPacket packet;
|
||||
for (; *p; p++)
|
||||
{
|
||||
if(decode_eggtimer_data(*p, &packet)){
|
||||
Serial.print("Data packet: ");
|
||||
if(packet.type == TRIGGER_NAME || packet.type == TRIGGER_CHANNEL){
|
||||
Serial.println(packet.data.name);
|
||||
}
|
||||
else{
|
||||
Serial.println(packet.data.altitude);
|
||||
bool buildPacket(EggtimerElement *eggt_packet, WhiteVestsPacket *wv_packet){
|
||||
static int16_t elements_received = 0;
|
||||
static WhiteVestsPacket nextPacket; // packet used when the packet is sent but it is not complete
|
||||
static bool sentIncomplete = false;
|
||||
|
||||
if(sentIncomplete){
|
||||
memcpy(wv_packet, &nextPacket, sizeof(WhiteVestsPacket));
|
||||
sentIncomplete = false;
|
||||
}
|
||||
|
||||
int16_t element_bit = NULL;
|
||||
double *element_pointer = NULL;
|
||||
double *nextElement_pointer = NULL;
|
||||
|
||||
|
||||
// convert data to double
|
||||
bool converted = true;
|
||||
char *endptr;
|
||||
double element = strtol((char*)&eggt_packet->data, &endptr, 10);
|
||||
if(endptr == (char*)&eggt_packet->data|| *endptr != NULL){ // error
|
||||
converted = false;
|
||||
element = 0;
|
||||
}
|
||||
|
||||
switch (eggt_packet->type){
|
||||
case TRIGGER_FLIGHT_TIME:
|
||||
element_bit = BIT_FLIGHT_TIME;
|
||||
element_pointer = &wv_packet->flight_time;
|
||||
nextElement_pointer = &nextPacket.flight_time;
|
||||
break;
|
||||
case TRIGGER_ALTITUDE_100:
|
||||
element_bit = BIT_ALTITUDE_100;
|
||||
element_pointer = &wv_packet->altitude;
|
||||
nextElement_pointer = &nextPacket.altitude;
|
||||
element *= 100.0;
|
||||
break;
|
||||
case TRIGGER_ALTITUDE:
|
||||
element_bit = BIT_ALTITUDE;
|
||||
element_pointer = &wv_packet->altitude;
|
||||
nextElement_pointer = &nextPacket.altitude;
|
||||
break;
|
||||
case TRIGGER_VELOCITY:
|
||||
element_bit = BIT_VELOCITY;
|
||||
element_pointer = &wv_packet->velocity;
|
||||
nextElement_pointer = &nextPacket.velocity;
|
||||
break;
|
||||
case TRIGGER_ACCELERATION_10:
|
||||
element_bit = BIT_ACCELERATION;
|
||||
element_pointer = &wv_packet->acceleration;
|
||||
nextElement_pointer = &nextPacket.acceleration;
|
||||
|
||||
element /= 10;
|
||||
break;
|
||||
case TRIGGER_FLIGHT_PHASE:
|
||||
element_bit = BIT_FLIGHT_PHASE;
|
||||
element_pointer = &wv_packet->flight_phase;
|
||||
nextElement_pointer = &nextPacket.flight_phase;
|
||||
break;
|
||||
case TRIGGER_TEMPERATURE_10:
|
||||
element_bit = BIT_TEMPERATURE_10;
|
||||
element_pointer = &wv_packet->temperature;
|
||||
nextElement_pointer = &nextPacket.temperature;
|
||||
element /= 10;
|
||||
break;
|
||||
case TRIGGER_BATERY_10:
|
||||
element_bit = BIT_BATERY_10;
|
||||
break;
|
||||
case TRIGGER_APOGEE:
|
||||
element_bit = BIT_APOGEE;
|
||||
break;
|
||||
case TRIGGER_MAX_VELOCITY:
|
||||
element_bit = BIT_MAX_VELOCITY;
|
||||
break;
|
||||
case TRIGGER_MAX_ACCELERATION:
|
||||
element_bit = BIT_MAX_ACCELERATION;
|
||||
break;
|
||||
case TRIGGER_CHANNEL:
|
||||
element_bit = BIT_CHANNEL;
|
||||
break;
|
||||
case TRIGGER_NAME:
|
||||
element_bit = BIT_NAME;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check if the data received is from current packet, if not, it will be stored in the next packet
|
||||
if((elements_received & element_bit) && element_pointer != NULL && nextElement_pointer != NULL && converted){
|
||||
memcpy(&nextPacket, wv_packet, sizeof(WhiteVestsPacket));
|
||||
*nextElement_pointer = element; // stores element in nextPacket
|
||||
elements_received = element_bit;
|
||||
sentIncomplete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( element_pointer != NULL){
|
||||
if(converted)
|
||||
*element_pointer = element;
|
||||
else
|
||||
return false; // error converting data
|
||||
}
|
||||
|
||||
|
||||
elements_received |= element_bit;
|
||||
|
||||
// check if the packet is complete
|
||||
if(elements_received == ALL_ELEMENTS){
|
||||
elements_received = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function send the packet to the server
|
||||
*/
|
||||
void sendPacket(WhiteVestsPacket * packet){
|
||||
unsigned char data_base64[255];
|
||||
encode_base64((unsigned char*)packet, sizeof(WhiteVestsPacket), data_base64);
|
||||
|
||||
char rssi_base64[] = "//8=";
|
||||
|
||||
Serial.print("T,");
|
||||
Serial.print((char *) data_base64);
|
||||
Serial.print(",");
|
||||
Serial.print((char *) rssi_base64);
|
||||
Serial.print("\n");
|
||||
|
||||
}
|
||||
|
||||
bool test(){
|
||||
char str[] ="{000>@1>#0000>~AB---->?079>!211>=KM6ZFL>"
|
||||
"{000>@1>#0000>~AB---->?079>!211>=KM6ZFL>"
|
||||
"{040>@2>#0010>~AB---->(0213>\\-001>?079>!212>=KM6ZFL>01>?079>!212>=KM6ZFL>"
|
||||
"{045>@2>#0014>~AB---->(0072>\\-001>?079>!212>=KM6ZFL>"
|
||||
"{046>@4>#0016>~AB---->(0033>\\-001>?079>!212>=KM6ZFL>"
|
||||
"{046>@5>#0018>~1B---->(2787>\\000>%04679>^0660>[025>?079>!210>=KM6ZFL> "
|
||||
"{045>@5>#0020>~1B---->(-0354>\\0=KM6ZFL>"
|
||||
"{043>@5>#0022>~1B---->(-0029>\\004>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{043>@5>#0024>~1B---->(-0023>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{042>@5>#0026>~1B---->(-0028>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{042>@5>#0028>~1B---->(-0035>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{041>@5>#0030>~1B---->(-0029>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{040>@5>#0032>~1B---->(-0032>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{040>@5>#0034>~1B---->(-0021>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{039>@5>#0036>~1B---->(-0028>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{038>@5>#0038>~1B---->(-0028>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{038>@5>#0040>~1B---->(-0031>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{037>@5>#0042>~1B---->(-0043>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{037>@5>#0044>~1B---->(-0040>\\000>%04679>^0660>[025>?079>!209>=KM6ZFL>"
|
||||
"{036>@5>#0046>~1B---->(-0022>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{035>@5>#0048>~1B---->(-0038>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{034>@5>#0050>~1B---->(-0028>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{034>@5>#0052>~1B---->(-0043>\\0=KM6ZFL>"
|
||||
"{033>@5>#0054>~1B---->(-0033>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{032>@5>#0056>~1B---->(-0041>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{032>@5>#0058>~1B---->(-0026>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{031>@5>#0060>~1B---->(-0035>\\000>%04679>^0660>[025>?079>!208>=KM6ZFL>"
|
||||
"{030>@5>#0062>~1B---->(-0037>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{029>@5>#0064>~1B---->(-0032>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{029>@5>#0066>~1B---->(-0035>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{028>@5>#0068>~1B---->(-0044>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{027>@5>#0070>~1B---->(-0045>\\000>%04679>^0660>[025>?079>!207>=KM6ZFL>"
|
||||
"{026>@5>#0072>~1B---->(-0035>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{025>@5>#0074>~1B---->(-0045>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{025>@5>#0076>~1B---->(-0043>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{024>@5>#0078>~1B---->(-0046>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{023>@5>#0080>~1B---->(-0046>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{022>@5>#0082>~1B---->(-0037>\\000>%04679>^0660>[025>?079>!206>=KM6ZFL>"
|
||||
"{021>@5>#0084>~1B---->(-0039>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{020>@5>#0086>~1B---->(-0043>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{020>@5>#0088>~1B---->(-0046>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{019>@5>#0090>~1B---->(-0037>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{018>@5>#0092>~1B---->(-0046>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{017>@5>#0094>~1B---->(-0035>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{016>@5>#0096>~1B---->(-0046>\\000>%04679>^0660>[025>?079>!205>=KM6ZFL>"
|
||||
"{016>@5>#0098>~1B---->(-0032>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{015>@5>#0100>~1B---->(-0043>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{014>@5>#0102>~1B---->(-0037>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{013>@5>#0104>~1B---->(-0033>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{012>@5>#0106>~1B---->(-0026>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{012>@5>#0108>~1B---->(-0029>\\000>%04679>^0660>[025>?079>!204>=KM6ZFL>"
|
||||
"{011>@5>#0110>~1B---->(-0042>\\000>%04679>^0660>[025>?079>!203>=KM6ZFL>"
|
||||
"{010>@5>#0112>~1B---->(-0041>\\000>%04679>^0660>[025>?079>!203>=KM6ZFL>"
|
||||
"{009>@5>#0114>~1B---->(-0037>\\000>%04679>^0660>[025>?079>!203>=KM6ZFL>"
|
||||
"{009>@5>#0116>~1B---->(-0040>\\000>%04679>^0660>[025>?079>!203>=KM6ZFL>"
|
||||
"{008>@5>#0118>~1B---->(-0043>\\000>%04679>^0660>[025>?079>!202>=KM6ZFL>";
|
||||
|
||||
char *p = str;
|
||||
EggtimerElement eggt_packet;
|
||||
WhiteVestsPacket wv_packet;
|
||||
memset(&wv_packet, 0, sizeof(WhiteVestsPacket));
|
||||
for (; *p; p++){
|
||||
if(decode_eggtimer_data(*p, &eggt_packet) && buildPacket(&eggt_packet, &wv_packet)){
|
||||
sendPacket(&wv_packet);
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.flush();
|
||||
while (!Serial);
|
||||
|
||||
Serial.println("Ready!");
|
||||
|
||||
Serial.println();
|
||||
test();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user