mirror of
https://github.com/OpenSolo/OpenSolo.git
synced 2025-04-29 22:24:32 +02:00
Initial commit, based on .tar.gz file as provided by 3DR , see SOURCE file
This commit is contained in:
commit
adbe6ebbf3
19
sololink/.clang-format
Normal file
19
sololink/.clang-format
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
BreakBeforeBinaryOperators: true
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
ColumnLimit: 100
|
||||||
|
Standard: Cpp11
|
||||||
|
IndentWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
#AlignConsecutiveAssignments: true # only available as of clang-format 3.7
|
||||||
|
BreakBeforeBinaryOperators: false
|
||||||
|
PointerAlignment: Right
|
||||||
|
# SpacesInAngles needed for nested templates (gcc 4.8.4 errors)
|
||||||
|
SpacesInAngles: true
|
||||||
|
---
|
||||||
|
# other lang configs here...
|
||||||
|
...
|
14
sololink/.gitignore
vendored
Normal file
14
sololink/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# osx junk
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
# object files
|
||||||
|
*.o
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# pycharm files
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# logs from sim
|
||||||
|
*.tlog
|
||||||
|
*.tlog.raw
|
||||||
|
*.parm
|
37
sololink/COPYRIGHT-3DR
Normal file
37
sololink/COPYRIGHT-3DR
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Copyright 2014-2017 3D Robotics
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use these files except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
Contributors prior to public release:
|
||||||
|
|
||||||
|
* Eric Liao
|
||||||
|
* John Finley
|
||||||
|
* Allan Matthew
|
||||||
|
* Will Silva
|
||||||
|
* Liam Staskawicz
|
||||||
|
* Angus Peart
|
||||||
|
* Igor Napolskikh
|
||||||
|
* Tim Ryan
|
||||||
|
* Peter Barker
|
||||||
|
* Robert Cottrell
|
||||||
|
* Ramón Roche
|
||||||
|
* Adam Setapen
|
||||||
|
* Kevin Mehall
|
||||||
|
* Jonathan Challinger
|
||||||
|
* Nick Speal
|
||||||
|
* Siddharth Bharat Purohit
|
||||||
|
* Andrew Tridgell
|
||||||
|
* Daniel Nugent
|
||||||
|
* Chavi Weingarten
|
202
sololink/LICENSE-APACHE
Normal file
202
sololink/LICENSE-APACHE
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
30
sololink/Makefile
Normal file
30
sololink/Makefile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
SUBDIRS = flightcode wifi
|
||||||
|
SUBDIRS_BUILD = $(SUBDIRS:%=%_build)
|
||||||
|
SUBDIRS_CLEAN = $(SUBDIRS:%=%_clean)
|
||||||
|
|
||||||
|
# SUBDIRS2 should eventually be same as SUBDIRS when all are formatted
|
||||||
|
|
||||||
|
SUBDIRS2 = flightcode
|
||||||
|
SUBDIRS_FMT = $(SUBDIRS2:%=%_fmt)
|
||||||
|
SUBDIRS_FMT_DIFF = $(SUBDIRS2:%=%_fmt-diff)
|
||||||
|
|
||||||
|
all: $(SUBDIRS_BUILD)
|
||||||
|
|
||||||
|
build: $(SUBDIRS_BUILD)
|
||||||
|
$(SUBDIRS_BUILD):
|
||||||
|
$(MAKE) -C $(@:%_build=%)
|
||||||
|
|
||||||
|
clean: $(SUBDIRS_CLEAN)
|
||||||
|
$(SUBDIRS_CLEAN):
|
||||||
|
$(MAKE) -C $(@:%_clean=%) clean
|
||||||
|
|
||||||
|
fmt: $(SUBDIRS_FMT)
|
||||||
|
$(SUBDIRS_FMT):
|
||||||
|
$(MAKE) -C $(@:%_fmt=%) fmt
|
||||||
|
|
||||||
|
fmt-diff: $(SUBDIRS_FMT_DIFF)
|
||||||
|
$(SUBDIRS_FMT_DIFF):
|
||||||
|
$(MAKE) -C $(@:%_fmt-diff=%) fmt-diff
|
||||||
|
|
||||||
|
.PHONY: $(SUBDIRS) $(SUBDIRS_BUILD) $(SUBDIRS_CLEAN) $(SUBDIRS_FMT) $(SUBDIRS_FMT_DIFF)
|
15
sololink/README.md
Normal file
15
sololink/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
WARNING - WORK IN PROGRESS
|
||||||
|
|
||||||
|
```
|
||||||
|
This code is known to be broken and/or incomplete. IT DOES NOT WORK.
|
||||||
|
|
||||||
|
We are actively working on fixing it, and we really, really do not recommend you download it just yet.
|
||||||
|
|
||||||
|
We will remove this warning from the repository when it is no longer required.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
SoloLink
|
||||||
|
========
|
||||||
|
|
||||||
|
Software for the SoloLink
|
1
sololink/SOURCE
Normal file
1
sololink/SOURCE
Normal file
@ -0,0 +1 @@
|
|||||||
|
This source code was released by 3DR in the file: sololink_2.2.4_ff71bb8f.tar.gz
|
292
sololink/STM32Loader/checkArtooAndUpdate.py
Executable file
292
sololink/STM32Loader/checkArtooAndUpdate.py
Executable file
@ -0,0 +1,292 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import serial
|
||||||
|
import slip
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import param_stored_vals_msg # Used for reading stick calibration values
|
||||||
|
import slip, struct # Used for writing stick calibration values
|
||||||
|
|
||||||
|
sololink_conf = "/etc/sololink.conf"
|
||||||
|
|
||||||
|
ARTOO_SYSINFO_ID = chr(0x3)
|
||||||
|
ARTOO_UPDATE_ID = chr(0x12)
|
||||||
|
ARTOO_LOCKOUT_ID = chr(0x13)
|
||||||
|
ARTOO_CALIBRATE_ID = chr(0x2)
|
||||||
|
|
||||||
|
ARTOO_UPDATE_SUCCESS = chr(1)
|
||||||
|
ARTOO_UPDATE_FAILED = chr(2)
|
||||||
|
|
||||||
|
ARTOO_LOCKOUT_FALSE = chr(0)
|
||||||
|
ARTOO_LOCKOUT_TRUE = chr(1)
|
||||||
|
|
||||||
|
ARTOO_BAUD = 115200
|
||||||
|
|
||||||
|
# update_result should be either ARTOO_UPDATE_SUCCESS or ARTOO_UPDATE_FAILED
|
||||||
|
def setArtooUpdateComplete(update_result):
|
||||||
|
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
|
||||||
|
slipdev = slip.SlipDevice(ser)
|
||||||
|
slipdev.write("".join([ARTOO_UPDATE_ID, update_result]))
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
# lockout should be either ARTOO_LOCKOUT_FALSE or ARTOO_LOCKOUT_TRUE
|
||||||
|
def setArtooLockout(lockout):
|
||||||
|
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
|
||||||
|
slipdev = slip.SlipDevice(ser)
|
||||||
|
slipdev.write("".join([ARTOO_LOCKOUT_ID, lockout]))
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
def mkdir_p(path):
|
||||||
|
try:
|
||||||
|
os.makedirs(path)
|
||||||
|
except OSError:
|
||||||
|
pass # already exists
|
||||||
|
|
||||||
|
def doUpdateComplete():
|
||||||
|
setArtooLockout(ARTOO_LOCKOUT_FALSE)
|
||||||
|
if not os.path.exists("/log/updates/READY"):
|
||||||
|
if os.path.exists("/log/updates/UPDATEFAILED"):
|
||||||
|
f = open("/log/updates/UPDATEFAILED", "r")
|
||||||
|
# file should be one line; read it all
|
||||||
|
r = f.read(1000)
|
||||||
|
f.close()
|
||||||
|
r = r.strip("\r\n\t\0 ")
|
||||||
|
logger.info("request \"update failed\" screen (%s)", r)
|
||||||
|
setArtooUpdateComplete(ARTOO_UPDATE_FAILED)
|
||||||
|
else:
|
||||||
|
logger.info("request \"update success\" screen")
|
||||||
|
setArtooUpdateComplete(ARTOO_UPDATE_SUCCESS)
|
||||||
|
mkdir_p("/log/updates")
|
||||||
|
open("/log/updates/READY", "w").close() # "touch"
|
||||||
|
else:
|
||||||
|
logger.info("no screen update (READY exists)")
|
||||||
|
|
||||||
|
# return tuple (filename, version), or None
|
||||||
|
def getFirmwareInfo():
|
||||||
|
files = sorted(glob.glob("/firmware/artoo_*.bin"))
|
||||||
|
if not files:
|
||||||
|
return None
|
||||||
|
filename = files[-1]
|
||||||
|
# Filename may be of the form
|
||||||
|
# "/firmware/artoo_0.0.0.bin", or
|
||||||
|
# "/firmware/artoo_v0.0.0.bin".
|
||||||
|
# Get it without the 'v'.
|
||||||
|
if filename[16] == 'v':
|
||||||
|
version = filename[17:-4]
|
||||||
|
else:
|
||||||
|
version = filename[16:-4]
|
||||||
|
return (filename, version)
|
||||||
|
|
||||||
|
# return version as string ("unknown" if can't get version)
|
||||||
|
def getArtooVersion():
|
||||||
|
#Check the version of the stm32 firmware over serial
|
||||||
|
#The STM32 might be emitting packets already, so we try a few times to get the
|
||||||
|
#version string. This has been observed to get the version with one retry on
|
||||||
|
#several occasions.
|
||||||
|
logger.info("requesting stm32 version")
|
||||||
|
version = "unknown"
|
||||||
|
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
|
||||||
|
slipdev = slip.SlipDevice(ser)
|
||||||
|
for i in range(5):
|
||||||
|
slipdev.write("".join([ARTOO_SYSINFO_ID]))
|
||||||
|
pkt = slipdev.read()
|
||||||
|
if not pkt:
|
||||||
|
logger.info("no data received from stm32, retrying")
|
||||||
|
continue
|
||||||
|
pkt = "".join(pkt)
|
||||||
|
if pkt[0] == ARTOO_SYSINFO_ID:
|
||||||
|
# SysInfo packet is: artoo/src/hostprotocol.cpp
|
||||||
|
# start size
|
||||||
|
# 0 1 ARTOO_SYSINFO_ID artoo/src/hostprotocol.h
|
||||||
|
# 1 12 UniqueId artoo/src/stm32/sys.h
|
||||||
|
# 13 2 hwversion artoo/src/hostprotocol.cpp
|
||||||
|
# 15 var Version artoo/src/version.h
|
||||||
|
# Version may start with an initial 'v', e.g. v0.6.10,
|
||||||
|
# but we want it starting with the numeric part.
|
||||||
|
if pkt[15] != 'v':
|
||||||
|
version = pkt[15:]
|
||||||
|
else:
|
||||||
|
version = pkt[16:]
|
||||||
|
break
|
||||||
|
logger.info("got %s/%d, retrying", str(hex(ord(pkt[0]))), len(pkt))
|
||||||
|
ser.close()
|
||||||
|
return version
|
||||||
|
|
||||||
|
# Attempt to update the STM32 multiple times
|
||||||
|
def updateStm32(filename):
|
||||||
|
# Total attempts will be retry+1, with the last one doing the unprotect.
|
||||||
|
# Since doing the unprotect results in a support call, retries is set
|
||||||
|
# such that the unprotect is a last resort. Allow for two init failures
|
||||||
|
# and two erase failures (truly degenerate) before doing the unprotect
|
||||||
|
# and (hopefully) fixing a protected stm32. Getting to the unprotect
|
||||||
|
# (where it is really needed) should happen only once in a controller's
|
||||||
|
# lifetime, if at all (it should never be needed, really).
|
||||||
|
stick_cal = None
|
||||||
|
retry = 4
|
||||||
|
while retry >= 0:
|
||||||
|
if retry > 0:
|
||||||
|
# don't unprotect chip the first few times an erase fails
|
||||||
|
logger.info("updating without readout protection disable")
|
||||||
|
if call_stm32loader(filename, disable_readout_protect=False):
|
||||||
|
break # Move on if the firmware is successfully loaded.
|
||||||
|
else:
|
||||||
|
# last attempt - unprotect chip if erase fails
|
||||||
|
logger.info("Updating the STM32 has failed several times. We suspect that readout protection is enabled.")
|
||||||
|
logger.info("Saving Stick Calibration: ")
|
||||||
|
stick_cal = read_stick_cal()
|
||||||
|
logger.info(stick_cal)
|
||||||
|
logger.info("updating with readout protection disable. This will wipe flash.")
|
||||||
|
call_stm32loader(filename, disable_readout_protect=True)
|
||||||
|
retry -= 1
|
||||||
|
|
||||||
|
logger.info("Update complete. Stick cal before update was: {}. (None means it wasn't read before update)".format(stick_cal))
|
||||||
|
logger.info("stick cal after update: {}".format(read_stick_cal()))
|
||||||
|
|
||||||
|
# Only write stick calibration if we did disabled readout protection:
|
||||||
|
if stick_cal is not None:
|
||||||
|
# Try to write_stick cal multiple times in case it fails.
|
||||||
|
# This is defensive. I've never seen it fail.
|
||||||
|
for i in range(3):
|
||||||
|
write_stick_cal(stick_cal)
|
||||||
|
time.sleep(1)
|
||||||
|
logger.info("Stick cal written. Reading it back again as a check:")
|
||||||
|
new_cal = read_stick_cal()
|
||||||
|
logger.info(new_cal)
|
||||||
|
if stick_cal == new_cal:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.info("Stick cal failed to be written correctly:")
|
||||||
|
logger.info("Calibration attempted to be written: {}".format(stick_cal))
|
||||||
|
logger.info("Calibration found in flash: {}".format(new_cal))
|
||||||
|
|
||||||
|
# Initiate the stm32loader process to actually update the STM32 chip. Return True if successful.
|
||||||
|
def call_stm32loader(filename, disable_readout_protect=False):
|
||||||
|
# subprocess.check_output raises an exception if stm32loader.py returns a nonzero exit code for any reason
|
||||||
|
# including if readout_protect is enabled.
|
||||||
|
# Disable readout protect if specified. This will erase all flash.
|
||||||
|
|
||||||
|
if disable_readout_protect:
|
||||||
|
flags = "-wvqu"
|
||||||
|
else:
|
||||||
|
flags = "-wvq"
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = subprocess.check_output(["stm32loader.py", flags, "-s", "127",
|
||||||
|
"-b", "115200", "-p", "/dev/ttymxc1", filename],
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
logger.info("stm32loader.py returned normally; output:")
|
||||||
|
success = True
|
||||||
|
except subprocess.CalledProcessError as cpe:
|
||||||
|
s = cpe.output
|
||||||
|
logger.info("stm32loader.py returned error; output:")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# this might be ugly, but it gets the output in the log
|
||||||
|
s = s.strip("\r\n\t\0 ")
|
||||||
|
logger.info(s)
|
||||||
|
# Wait a second for the STM32 to come back up before we send it a message later
|
||||||
|
time.sleep(1)
|
||||||
|
return success
|
||||||
|
|
||||||
|
def writeVersionFile(version):
|
||||||
|
f = open("/STM_VERSION", 'w')
|
||||||
|
f.write(version + '\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# return version from /STM_VERSION
|
||||||
|
def getVersionFile():
|
||||||
|
try:
|
||||||
|
f = open("/STM_VERSION", 'r')
|
||||||
|
version = f.readline()
|
||||||
|
f.close()
|
||||||
|
version = version.strip()
|
||||||
|
if version == "":
|
||||||
|
version = "unknown"
|
||||||
|
except:
|
||||||
|
version = "unknown"
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def read_stick_cal():
|
||||||
|
# Start the STM32 process because sololink_config sets runlevel to 2 before an update,
|
||||||
|
# but we need the (runlevel 3) STM32 process to read a stick cal.
|
||||||
|
p = subprocess.Popen('stm32', shell=True)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
msg = param_stored_vals_msg.fetch() # String containing all artoo params (including stick cal values)
|
||||||
|
p.terminate() # Kill the STM32 process.
|
||||||
|
time.sleep(1) # Give time for the STM32 process to die. Not sure if needed.
|
||||||
|
|
||||||
|
# msg is an empty string if the stm32 process is not running (runlevel 2)
|
||||||
|
if not msg:
|
||||||
|
logger.info("Could not read stick calibration.")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
msg = param_stored_vals_msg.unpack(msg) # Converts string to Dict
|
||||||
|
axes = msg.get('stickCals') # 6-item list of 3-item tuples
|
||||||
|
# Pack datastructure into a single list that can be written later
|
||||||
|
stick_cals = []
|
||||||
|
for a in axes:
|
||||||
|
stick_cals.extend(a)
|
||||||
|
return stick_cals
|
||||||
|
|
||||||
|
|
||||||
|
def write_stick_cal(values):
|
||||||
|
logger.info("Writing stick cal: {}".format(values))
|
||||||
|
|
||||||
|
serialpath = '/dev/ttymxc1'
|
||||||
|
serialport = serial.Serial(serialpath, ARTOO_BAUD)
|
||||||
|
slipdev = slip.SlipDevice(serialport)
|
||||||
|
|
||||||
|
packed_vals = struct.pack("<HHHHHHHHHHHHHHHHHH", *values)
|
||||||
|
slipdev.write("".join([ARTOO_CALIBRATE_ID, packed_vals]))
|
||||||
|
|
||||||
|
|
||||||
|
# Main
|
||||||
|
logging.config.fileConfig(sololink_conf)
|
||||||
|
logger = logging.getLogger("stm32")
|
||||||
|
|
||||||
|
logger.info("stm32 update starting")
|
||||||
|
|
||||||
|
firmware = getFirmwareInfo()
|
||||||
|
|
||||||
|
if firmware is None:
|
||||||
|
logger.info("no firmware available for update")
|
||||||
|
else:
|
||||||
|
logger.info("firmware: file %s, version %s", firmware[0], firmware[1])
|
||||||
|
|
||||||
|
# Read the version from the STM32
|
||||||
|
artoo_version = getArtooVersion()
|
||||||
|
logger.info("running version: %s", artoo_version)
|
||||||
|
|
||||||
|
# If we have firmware and it does not match what is running, update the STM32
|
||||||
|
if firmware is not None:
|
||||||
|
if artoo_version != firmware[1]:
|
||||||
|
logger.info("updating")
|
||||||
|
updateStm32(firmware[0])
|
||||||
|
# re-read the version from the running firmware
|
||||||
|
artoo_version = getArtooVersion()
|
||||||
|
else:
|
||||||
|
logger.info("not updating (new firmware is already running)")
|
||||||
|
# Whether we used it or not, we are done with the new firmware
|
||||||
|
logger.info("moving firmware to loaded")
|
||||||
|
mkdir_p("/firmware/loaded")
|
||||||
|
os.rename(firmware[0], "/firmware/loaded/" + os.path.basename(firmware[0]))
|
||||||
|
else:
|
||||||
|
logger.info("not updating (no new firmware)")
|
||||||
|
|
||||||
|
# Write version retrieved from STM32 to file
|
||||||
|
logger.info("writing STM_VERSION with running version %s", artoo_version)
|
||||||
|
writeVersionFile(artoo_version)
|
||||||
|
|
||||||
|
doUpdateComplete()
|
||||||
|
|
||||||
|
# delete /log/.factory if it exists (it has no effect)
|
||||||
|
if os.path.exists("/log/.factory"):
|
||||||
|
logger.info("deleting .factory")
|
||||||
|
os.system("rm -f /log/.factory")
|
96
sololink/STM32Loader/checkArtooAndUpdateTest
Executable file
96
sololink/STM32Loader/checkArtooAndUpdateTest
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Conditions:
|
||||||
|
# Firmware file: does not exist, exists and is same, exists and is different
|
||||||
|
# STM_VERSION: does not exist, exists
|
||||||
|
|
||||||
|
# start out with nothing in /firmware,
|
||||||
|
# and two versions in /firmware/loaded
|
||||||
|
version_1="0.6.14"
|
||||||
|
version_2="0.7.3"
|
||||||
|
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
init 2
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# start with version_1 running
|
||||||
|
date +%H:%M:%S
|
||||||
|
stm32loader.py -wvq -s 127 -b 115200 -p /dev/ttymxc1 /firmware/loaded/artoo_${version_1}.bin
|
||||||
|
date +%H:%M:%S
|
||||||
|
|
||||||
|
# test: (firmware file, STM_VERSION)
|
||||||
|
|
||||||
|
# ***** should not happen
|
||||||
|
echo
|
||||||
|
echo "TEST: no update, /STM_VERSION does not exist"
|
||||||
|
rm /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_1}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
# test: (does not exist, exists)
|
||||||
|
# ***** common boot case
|
||||||
|
echo
|
||||||
|
echo "TEST: no update, /STM_VERSION exists"
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_1}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
# ***** possible first-boot case
|
||||||
|
echo
|
||||||
|
echo "TEST: update exists but is same, /STM_VERSION does not exist"
|
||||||
|
mv /firmware/loaded/artoo_${version_1}.bin /firmware
|
||||||
|
rm /STM_VERSION
|
||||||
|
echo "${version_1} in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_1}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
# ***** uncommon update case
|
||||||
|
echo
|
||||||
|
echo "TEST: update exists but is same, /STM_VERSION exists"
|
||||||
|
mv /firmware/loaded/artoo_${version_1}.bin /firmware
|
||||||
|
echo "${version_1} in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_1}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
# ***** possible first-boot case
|
||||||
|
echo
|
||||||
|
echo "TEST: update exists and is different, /STM_VERSION does not exist"
|
||||||
|
mv /firmware/loaded/artoo_${version_2}.bin /firmware
|
||||||
|
rm /STM_VERSION
|
||||||
|
echo "${version_2} in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_2}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
|
||||||
|
# ***** common update case
|
||||||
|
echo
|
||||||
|
echo "TEST: update exists and is different, /STM_VERSION exists"
|
||||||
|
mv /firmware/loaded/artoo_${version_1}.bin /firmware
|
||||||
|
echo "${version_1} in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
echo "/STM_VERSION should be ${version_1}:"
|
||||||
|
cat /STM_VERSION
|
||||||
|
echo "nothing in /firmware:"
|
||||||
|
find /firmware -name \*.bin
|
63
sololink/STM32Loader/reset_artoo
Executable file
63
sololink/STM32Loader/reset_artoo
Executable file
@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# -d 'N': delay 'N' seconds during reset
|
||||||
|
# -n: don't kill stm32 process
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "usage: reset_artoo [-d N] [-n]"
|
||||||
|
echo " -d N delay N seconds with reset asserted"
|
||||||
|
echo " -n don't kill the stm32 process (debug usage)"
|
||||||
|
}
|
||||||
|
|
||||||
|
delay=0
|
||||||
|
dokill=true
|
||||||
|
until [ -z "${1}" ]; do
|
||||||
|
case ${1} in
|
||||||
|
-d)
|
||||||
|
if [ -z "${2}" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
delay="${2}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n)
|
||||||
|
dokill=false
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
pin_boot=45
|
||||||
|
pin_reset=46
|
||||||
|
|
||||||
|
set_pin() {
|
||||||
|
pushd /sys/class/gpio > /dev/null
|
||||||
|
if [ ! -e gpio${1} ]; then
|
||||||
|
echo "${1}" > /sys/class/gpio/export
|
||||||
|
fi
|
||||||
|
echo "out" > /sys/class/gpio/gpio${1}/direction
|
||||||
|
echo "${2}" > /sys/class/gpio/gpio${1}/value
|
||||||
|
popd > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ${dokill}; then
|
||||||
|
# stop it
|
||||||
|
init 2
|
||||||
|
# wait for it to be truly gone
|
||||||
|
until ! killall -q -CONT stm32; do
|
||||||
|
true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# deassert boot
|
||||||
|
set_pin ${pin_boot} 0
|
||||||
|
|
||||||
|
# pulse reset
|
||||||
|
set_pin ${pin_reset} 1
|
||||||
|
sleep ${delay}
|
||||||
|
set_pin ${pin_reset} 0
|
||||||
|
|
||||||
|
if ${dokill}; then
|
||||||
|
# restart everything
|
||||||
|
init 3
|
||||||
|
fi
|
36
sololink/STM32Loader/stm32_check_cal.py
Executable file
36
sololink/STM32Loader/stm32_check_cal.py
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# stm32loader in /usr/bin is not on the default python path; make
|
||||||
|
# sure we always find it no matter where this script is installed
|
||||||
|
import sys
|
||||||
|
sys.path.append("/usr/bin")
|
||||||
|
import stm32loader
|
||||||
|
|
||||||
|
stm32loader.QUIET = 0
|
||||||
|
|
||||||
|
cmd = stm32loader.CommandInterface()
|
||||||
|
cmd.open("/dev/ttymxc1", 115200)
|
||||||
|
cmd.initChip()
|
||||||
|
cmd.cmdGet()
|
||||||
|
cmd.cmdGetID()
|
||||||
|
|
||||||
|
status = 1
|
||||||
|
|
||||||
|
msg = "cal is blank"
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = cmd.readMemory(0x0803f800, 2048)
|
||||||
|
for c in data:
|
||||||
|
if c != 255:
|
||||||
|
msg = "cal is not blank"
|
||||||
|
status = 0
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
msg = "error reading cal (readout protect?)"
|
||||||
|
pass
|
||||||
|
|
||||||
|
cmd.releaseChip()
|
||||||
|
|
||||||
|
print msg
|
||||||
|
|
||||||
|
sys.exit(status)
|
15
sololink/STM32Loader/stm32_readout_protect.py
Executable file
15
sololink/STM32Loader/stm32_readout_protect.py
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# stm32loader in /usr/bin is not on the default python path; make
|
||||||
|
# sure we always find it no matter where this script is installed
|
||||||
|
import sys
|
||||||
|
sys.path.append("/usr/bin")
|
||||||
|
import stm32loader
|
||||||
|
|
||||||
|
cmd = stm32loader.CommandInterface()
|
||||||
|
cmd.open("/dev/ttymxc1", 115200)
|
||||||
|
cmd.initChip()
|
||||||
|
cmd.cmdGet()
|
||||||
|
cmd.cmdGetID()
|
||||||
|
cmd.cmdReadoutProtect()
|
||||||
|
cmd.releaseChip()
|
14
sololink/STM32Loader/stm32_reset.py
Executable file
14
sololink/STM32Loader/stm32_reset.py
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# stm32loader in /usr/bin is not on the default python path; make
|
||||||
|
# sure we always find it no matter where this script is installed
|
||||||
|
import sys
|
||||||
|
sys.path.append("/usr/bin")
|
||||||
|
import stm32loader
|
||||||
|
|
||||||
|
cmd = stm32loader.CommandInterface()
|
||||||
|
cmd.open("/dev/ttymxc1", 115200)
|
||||||
|
cmd.initChip()
|
||||||
|
cmd.cmdGet()
|
||||||
|
cmd.cmdGetID()
|
||||||
|
cmd.releaseChip()
|
149
sololink/STM32Loader/stm32_update_test.sh
Executable file
149
sololink/STM32Loader/stm32_update_test.sh
Executable file
@ -0,0 +1,149 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# STM32 Update Testing
|
||||||
|
#
|
||||||
|
# forever:
|
||||||
|
# "update" to factory
|
||||||
|
# verify calibration
|
||||||
|
# update to new
|
||||||
|
# verify calibration
|
||||||
|
#
|
||||||
|
# Setup:
|
||||||
|
# Put factory and latest firmware on controller in /firmware/loaded.
|
||||||
|
# Copy this script to controller and run it there.
|
||||||
|
# Set names of those two files in this script (below).
|
||||||
|
#
|
||||||
|
# Results go to stdout. Run it like this:
|
||||||
|
# # ./stm32_update_test.sh 2>&1 | tee -a /log/stm32_test.log &
|
||||||
|
# to get a log (of both stdout and stderr) and let you disconnect from
|
||||||
|
# controller while the test runs. If cal is ever detected as missing,
|
||||||
|
# the test stops.
|
||||||
|
#
|
||||||
|
# BIG NOTE:
|
||||||
|
#
|
||||||
|
# If you run the script in the background (&), then it is "difficult"
|
||||||
|
# (I could not figure out how) to stop it without leaving the STM32 in
|
||||||
|
# a corrupt state (no program to run). If you turn off controller in
|
||||||
|
# this state, you will have to OPEN IT UP AND FLASH THE STM32 MANUALLY
|
||||||
|
# to fix it. "Safely" (relative, right) terminate the test like this:
|
||||||
|
# # ./stm32_update_test.sh 2>&1 | tee -a /log/stm32_test.log &
|
||||||
|
# [1] 5970
|
||||||
|
# #
|
||||||
|
# (time passes)
|
||||||
|
# # kill %1
|
||||||
|
# (STM32 is mostly likely CORRUPT at this point)
|
||||||
|
# # mv /firmware/loaded/artoo_1.2.8.bin /firmware
|
||||||
|
# # checkArtooAndUpdate.py
|
||||||
|
# (STM32 is flashed)
|
||||||
|
#
|
||||||
|
# Little Notes:
|
||||||
|
# * Stm32 is normally receiving data (mavlink) at the moment the update
|
||||||
|
# process begins. One thought is that data might somehow remain in the
|
||||||
|
# uart (imx6 -> STM32), causing problems in init. This test probably
|
||||||
|
# does not cover that, but another test that *did* catch that shows
|
||||||
|
# that the effect is to fail to flash artoo - cal is not erased.
|
||||||
|
# * After killing this test, you may be in runlevel 2, which means the
|
||||||
|
# controller's power button will NOT work; do an "init 3" then it will
|
||||||
|
# work. If you control-C, the script will finish the current update
|
||||||
|
# (up to a minute) and quit, leaving you in runlevel 3.
|
||||||
|
|
||||||
|
factory=artoo_1.2.2.bin
|
||||||
|
latest=artoo_1.2.8.bin
|
||||||
|
|
||||||
|
# $1 is start address
|
||||||
|
# $2 is length
|
||||||
|
# $3 is output file
|
||||||
|
stm32_read() {
|
||||||
|
init 2
|
||||||
|
sleep 2
|
||||||
|
stm32loader.py -q -p /dev/ttymxc1 -r -a $1 -l $2 $3
|
||||||
|
sleep 1
|
||||||
|
init 3
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 is the firmware filename
|
||||||
|
stm32_update() {
|
||||||
|
cp /firmware/loaded/$1 /firmware
|
||||||
|
init 2
|
||||||
|
sleep 2
|
||||||
|
checkArtooAndUpdate.py
|
||||||
|
init 3
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
|
||||||
|
check_cal() {
|
||||||
|
stm32_read 0x0803f800 2048 /log/cal.bin
|
||||||
|
if cmp -s /log/cal.bin /log/cal-ref.bin; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "cal read from STM32 does not match reference cal"
|
||||||
|
echo "reference cal:"
|
||||||
|
od -x /log/cal-ref.bin
|
||||||
|
echo "cal read from STM32:"
|
||||||
|
od -x /log/cal.bin
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check required files are there
|
||||||
|
if [ ! -f /firmware/loaded/${factory} -o \
|
||||||
|
! -f /firmware/loaded/${latest} ]; then
|
||||||
|
echo "${factory} or ${latest} not found in /firmware/loaded"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get reference cal
|
||||||
|
echo "reading reference cal..."
|
||||||
|
stm32_read 0x0803f800 2048 /log/cal-ref.bin
|
||||||
|
if [ `od -x /log/cal-ref.bin | wc -l` -le 3 ]; then
|
||||||
|
# cal looks erased already
|
||||||
|
echo "reference cal already erased?"
|
||||||
|
od -x /log/cal-ref.bin
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get erased block (assumes block before cal is erased)
|
||||||
|
#echo "reading erased block..."
|
||||||
|
#stm32_read 0x0803f000 2048 /log/cal-bad.bin
|
||||||
|
#if [ `od -x /log/cal-bad.bin | wc -l` -ne 3 ]; then
|
||||||
|
# # does not look erased
|
||||||
|
# echo "erased block isn't erased?"
|
||||||
|
# od -x /log/cal-bad.bin
|
||||||
|
# exit 1
|
||||||
|
#fi
|
||||||
|
|
||||||
|
# set up so control-C (SIGINT) or kill (SIGTERM) exits between updates
|
||||||
|
quitting=false
|
||||||
|
|
||||||
|
on_sigint() {
|
||||||
|
echo "sigint..."
|
||||||
|
quitting=true
|
||||||
|
}
|
||||||
|
trap on_sigint SIGINT
|
||||||
|
|
||||||
|
# The intent is to be able to background this script, then tell it to quit
|
||||||
|
# with "kill %1" to send it a SIGTERM, but this never gets called... :(
|
||||||
|
on_sigterm() {
|
||||||
|
echo "sigterm..."
|
||||||
|
quitting=true
|
||||||
|
}
|
||||||
|
trap on_sigterm SIGTERM
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
|
||||||
|
echo "updating to ${factory}..."
|
||||||
|
stm32_update ${factory}
|
||||||
|
echo "checking cal..."
|
||||||
|
check_cal
|
||||||
|
|
||||||
|
if [ $quitting == "true" ]; then break; fi
|
||||||
|
|
||||||
|
echo "updating to ${latest}..."
|
||||||
|
stm32_update ${latest}
|
||||||
|
echo "checking cal..."
|
||||||
|
check_cal
|
||||||
|
|
||||||
|
if [ $quitting == "true" ]; then break; fi
|
||||||
|
|
||||||
|
done
|
24
sololink/STM32Loader/test_save_stick_cal.sh
Executable file
24
sololink/STM32Loader/test_save_stick_cal.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Purpose of this test
|
||||||
|
# Enable readout protect and then attempt an artoo update.
|
||||||
|
# Artoo parameters (i.e. stick cals) should not be wiped.
|
||||||
|
# Test for IG-1435
|
||||||
|
|
||||||
|
# Note that this test doesn't always work
|
||||||
|
# because for some reason I need to run stm32_readout_protect.py a couple times for it to take effect,
|
||||||
|
# but it should be kept around as inspiration for how to test this functionality manually.
|
||||||
|
# Testing via a hard power cycle could be more representative than reboot anyway
|
||||||
|
|
||||||
|
# Preconditions for this test:
|
||||||
|
# Controller is on Solo v2.4.2-1, with Artoo 1.2.11.
|
||||||
|
# A firmware file for Artoo 1.2.11 named 1.2.22 exists in /firmware/loaded
|
||||||
|
# stm32_readout_protect.py and this file have been copied from the sololink repo (stm32loader folder) into artoo:/usr/bin
|
||||||
|
|
||||||
|
init 2 # Stop the stm32 process
|
||||||
|
sleep 5
|
||||||
|
cp /firmware/loaded/artoo_1.2.12.bin /firmware/
|
||||||
|
python /usr/bin/stm32_readout_protect.py # Enable Readout Protect
|
||||||
|
echo "Rebooting the controller. Once it comes up, check /log/3dr-solo.log for test results."
|
||||||
|
sync
|
||||||
|
reboot
|
28
sololink/STM32Loader/updateArtoo.sh
Executable file
28
sololink/STM32Loader/updateArtoo.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#Comment out the RCTX line in /etc/inittab
|
||||||
|
sed -i 's/^RCTX.*/#&/g' /etc/inittab
|
||||||
|
|
||||||
|
#stop the stm32 process
|
||||||
|
init q
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# update artoo
|
||||||
|
# we only erase the first 127 pages since the stm32 stores param data in its last page.
|
||||||
|
# the stm32 we're currently using has 128 pages of 2k each - this will need to change if we
|
||||||
|
# ever use different stm32 varieties.
|
||||||
|
stm32loader.py -vw -s 127 -p /dev/ttymxc1 /home/root/artoo.bin
|
||||||
|
|
||||||
|
# note - verification
|
||||||
|
# you can read back the last page to verify that it wasn't overwritten in the update process:
|
||||||
|
#
|
||||||
|
# ./stm32loader.py -r -a 0x803f800 -l 2048 -p /dev/ttymxc1 readback.bin
|
||||||
|
# hexdump readback.bin
|
||||||
|
#
|
||||||
|
# if it's not all 0xff's you're in good shape!
|
||||||
|
|
||||||
|
#Uncomment the RCTX line
|
||||||
|
sed -i 's/.\(RCTX*.\)/\1/g' /etc/inittab
|
||||||
|
|
||||||
|
#start the stm32 process back up
|
||||||
|
init q
|
34
sololink/config/checknet
Executable file
34
sololink/config/checknet
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Verify network device exists, and reboot if it does not
|
||||||
|
|
||||||
|
# This is a workaround for the case where the PCIe phy does not initialize,
|
||||||
|
# which happens a few percent of the time. The user-facing effect is that
|
||||||
|
# startup takes about 20 seconds longer if the phy does not initialize.
|
||||||
|
#
|
||||||
|
# It would be better to do this from the initrd.
|
||||||
|
#
|
||||||
|
# It would be better still to fix the PCIe driver to reset and try again (or
|
||||||
|
# something). Simple fixes (e.g. increasing the training time) did not work.
|
||||||
|
# The imx6 PCIe driver in the 3.14.38 BSP is considerably different from the
|
||||||
|
# one in the 3.10.17 BSP, so perhaps it will be fixed when we update.
|
||||||
|
|
||||||
|
kmsg() {
|
||||||
|
echo "$@" > /dev/kmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
# wlan0 exists on both solo and controller
|
||||||
|
# (although normally it is not used on controller)
|
||||||
|
kmsg -n "Checking wlan0... "
|
||||||
|
|
||||||
|
if [ ! -e /sys/class/net/wlan0 ]; then
|
||||||
|
kmsg "NOT FOUND"
|
||||||
|
# all logs except boot have been rotated at this point;
|
||||||
|
# stop and rotate it keep them all in sync
|
||||||
|
/etc/init.d/bootlogd stop
|
||||||
|
shutdown -r now
|
||||||
|
# don't let any more startup scripts start
|
||||||
|
sleep 60
|
||||||
|
else
|
||||||
|
kmsg "OK"
|
||||||
|
fi
|
285
sololink/config/configinit
Executable file
285
sololink/config/configinit
Executable file
@ -0,0 +1,285 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# SoloLink Configuration file validate/restore
|
||||||
|
#
|
||||||
|
# Called at init time before any config files are used via an entry in
|
||||||
|
# /etc/rcS.d.
|
||||||
|
|
||||||
|
cd /etc
|
||||||
|
|
||||||
|
verbose=false
|
||||||
|
|
||||||
|
# arguments
|
||||||
|
# -v verbose
|
||||||
|
until [ -z "$1" ]; do
|
||||||
|
case $1 in
|
||||||
|
-v) verbose=true;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
dmsg() {
|
||||||
|
if ${verbose}; then echo "$@"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test mode: Ubuntu md5sum is a bit different from busybox md5sum
|
||||||
|
# /usr/bin/md5sum is a symlink if it points to busybox
|
||||||
|
if [ -h `which md5sum` ]; then
|
||||||
|
# assume busybox
|
||||||
|
# not test mode
|
||||||
|
MD5SUM="md5sum -cs"
|
||||||
|
ETC="/etc"
|
||||||
|
ROFS="/mnt/rootfs.ro"
|
||||||
|
kmsg() {
|
||||||
|
echo "$@" > /dev/kmsg
|
||||||
|
}
|
||||||
|
else
|
||||||
|
# assume not busybox; correct for ubuntu but not tested on os-x
|
||||||
|
# test mode
|
||||||
|
MD5SUM="md5sum -c --status"
|
||||||
|
ETC="./etc"
|
||||||
|
ROFS="./rootfs.ro"
|
||||||
|
kmsg() {
|
||||||
|
echo "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
kmsg -n "Checking SoloLink configuration... "
|
||||||
|
dmsg ""
|
||||||
|
|
||||||
|
config_names="hostapd wpa_supplicant sololink shotmanager"
|
||||||
|
|
||||||
|
# For each file "file" in ${config_names}:
|
||||||
|
#
|
||||||
|
# A new FS contains only "file.orig"
|
||||||
|
#
|
||||||
|
# First boot:
|
||||||
|
# 1. file.conf does not exist
|
||||||
|
# 2. file.back does not exist
|
||||||
|
# 3. copy file.orig to file.base (used for upgrade merge)
|
||||||
|
# 4. copy file.orig to file.conf
|
||||||
|
# 5. create file.conf.md5
|
||||||
|
#
|
||||||
|
# Subsequent boots:
|
||||||
|
# 1. file.conf exists
|
||||||
|
# 2. file.conf.md5 is good
|
||||||
|
#
|
||||||
|
# Program modification of a config file (not in this script):
|
||||||
|
# 1. copy file.conf to file.back
|
||||||
|
# 2. create file.back.md5
|
||||||
|
# 3. modify file.conf
|
||||||
|
# 4. create file.conf.md5
|
||||||
|
#
|
||||||
|
# Manual modification of a config file (e.g. via console or ssh):
|
||||||
|
# 1. edit file.conf
|
||||||
|
# 2. md5sum <full-path-to-file.conf> > <full-path-to-file.conf.md5>
|
||||||
|
#
|
||||||
|
# During the modification process, a shutdown can result in any of the
|
||||||
|
# following (numbers match completion of steps above):
|
||||||
|
# 1. file.conf valid, file.back invalid (file.conf is used)
|
||||||
|
# 2. file.conf valid, file.back valid (file.conf used)
|
||||||
|
# 3. file.conf invalid, file.back valid (file.conf rollback)
|
||||||
|
# 4. file.conf valid, file.back valid (file.conf used)
|
||||||
|
#
|
||||||
|
# Example recovery:
|
||||||
|
# 1. file.conf exists
|
||||||
|
# 2. file.conf.md5 is not good
|
||||||
|
# 3. file.back exists
|
||||||
|
# 4. file.back.md5 is good
|
||||||
|
# 5. copy file.back to file.conf
|
||||||
|
# 6. create file.conf.md5
|
||||||
|
#
|
||||||
|
# Example upgrade:
|
||||||
|
# 1. file.conf exists
|
||||||
|
# 2. file.conf.md5 is good
|
||||||
|
# 3. file.orig.md5 exists
|
||||||
|
# 4. file.orig.md5 is not good (because file.orig changed)
|
||||||
|
# 5. patch = file.base -> file.conf; previous changes (e.g. setting ssid)
|
||||||
|
# 6. apply patch to new file.orig, creating file.conf
|
||||||
|
# 7. create file.conf.md5
|
||||||
|
# 8. create file.orig.md5
|
||||||
|
# 9. copy file.orig to file.base
|
||||||
|
# 10. create file.base.md5
|
||||||
|
#
|
||||||
|
# Example upgrade (merge fails):
|
||||||
|
# 1. file.conf exists
|
||||||
|
# 2. file.conf.md5 is good
|
||||||
|
# 3. file.orig.md5 exists
|
||||||
|
# 4. file.orig.md5 is not good (because file.orig changed)
|
||||||
|
# 5. file.base is not good (does not exist)
|
||||||
|
# 6. copy file.orig to file.conf
|
||||||
|
# 7. create file.conf.md5
|
||||||
|
# 8. create file.orig.md5
|
||||||
|
# 9. copy file.orig to file.base
|
||||||
|
# 10. create file.base.md5
|
||||||
|
|
||||||
|
# Return true if file exists, md5 exists, and the md5 is correct
|
||||||
|
valid() {
|
||||||
|
[ -f ${1} ] && [ -f ${1}.md5 ] && ${MD5SUM} ${1}.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return true if orig.md5 does not exist, or it exists but is not correct
|
||||||
|
upgraded() {
|
||||||
|
[ ! -f ${1}.orig.md5 ] || ! ${MD5SUM} ${1}.orig.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create backup of .conf
|
||||||
|
do_backup() {
|
||||||
|
dmsg "backup ${1}.conf -> ${1}.back"
|
||||||
|
cp ${1}.conf ${1}.back
|
||||||
|
md5sum `realpath ${1}.back` > ${1}.back.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore one file to another, i.e. .back or .orig to .conf
|
||||||
|
do_restore() {
|
||||||
|
dmsg "copy ${1} -> ${2}"
|
||||||
|
cp ${1} ${2}
|
||||||
|
md5sum `realpath ${2}` > ${2}.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note that the busybox 'patch' we are using is relatively limited:
|
||||||
|
# patch [-p NUM] [-i DIFF] [-R] [-N]
|
||||||
|
# -p NUM Strip NUM leading components from file names
|
||||||
|
# -i DIFF Read DIFF instead of stdin
|
||||||
|
# -R Reverse patch
|
||||||
|
# -N Ignore already applied patches
|
||||||
|
#
|
||||||
|
# Busybox patch's return status is not documented. GNU patch's return is this:
|
||||||
|
# patch's exit status is 0 if all hunks are applied successfully, 1 if some
|
||||||
|
# hunks cannot be applied or there were merge conflicts, and 2 if there is
|
||||||
|
# more serious trouble.
|
||||||
|
# We assume here it is zero on success, nonzero on any failure.
|
||||||
|
do_patch() {
|
||||||
|
#dmsg "patch ${1}.conf"
|
||||||
|
patch ${1}.conf < ${1}.patch
|
||||||
|
# patch's return code is this function's return code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Merge changes.
|
||||||
|
#
|
||||||
|
# config.base is a copy of the previous config.orig (before this update)
|
||||||
|
# config.conf is that, plus any changes the user has made
|
||||||
|
# config.orig is the _new_ config for this update
|
||||||
|
#
|
||||||
|
# 1. config.base -> config.conf is what has changed from the previous
|
||||||
|
# config.orig to the previous config.conf. Create a patch from that.
|
||||||
|
#
|
||||||
|
# 2. Attempt to apply the patch to the _new_ config.orig.
|
||||||
|
#
|
||||||
|
# a. If the patch applies cleanly, the new config.conf is the new
|
||||||
|
# config.orig plus patch.
|
||||||
|
#
|
||||||
|
# b. If the patch does not apply cleanly, the new config.conf is just
|
||||||
|
# copied from the new config.orig (changes are lost).
|
||||||
|
#
|
||||||
|
# Argument is the config file name root, e.g. "hostapd" means we are working
|
||||||
|
# with hostapd.base, hostapd.conf, and hostapd.orig.
|
||||||
|
#
|
||||||
|
do_merge() {
|
||||||
|
dmsg "merge ${1}"
|
||||||
|
|
||||||
|
# config.conf is known to be valid at this point
|
||||||
|
|
||||||
|
if valid ${1}.base; then
|
||||||
|
# config.base is valid; try to merge changes into new config
|
||||||
|
|
||||||
|
# config.patch is the user's changes in the pre-upgrade system
|
||||||
|
diff -au ${1}.base ${1}.conf > ${1}.patch
|
||||||
|
|
||||||
|
# start with upgraded config.conf...
|
||||||
|
do_backup ${1}
|
||||||
|
cp ${1}.orig ${1}.conf
|
||||||
|
|
||||||
|
# apply user's changes
|
||||||
|
if ! do_patch ${1}; then
|
||||||
|
# some hunks failed - partial patching seems confusing, so revert it all
|
||||||
|
dmsg "patch ${1}.conf failed"
|
||||||
|
dmsg "copy ${1}.orig -> ${1}.conf"
|
||||||
|
cp ${1}.orig ${1}.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .conf is good now, either updated or just copied
|
||||||
|
md5sum `realpath ${1}.conf` > ${1}.conf.md5
|
||||||
|
rm -f ${1}.patch ${1}.back* ${1}.conf.orig
|
||||||
|
|
||||||
|
else
|
||||||
|
# config.base is not valid, can't merge
|
||||||
|
dmsg "${1}.base not valid; can't merge"
|
||||||
|
dmsg "copy ${1}.orig -> ${1}.conf"
|
||||||
|
cp ${1}.orig ${1}.conf
|
||||||
|
md5sum `realpath ${1}.conf` > ${1}.conf.md5
|
||||||
|
fi
|
||||||
|
|
||||||
|
# this is how we detect when the next upgrade happens
|
||||||
|
md5sum `realpath ${1}.orig` > ${1}.orig.md5
|
||||||
|
|
||||||
|
# new config.base will be used in the next upgrade
|
||||||
|
cp ${1}.orig ${1}.base
|
||||||
|
md5sum `realpath ${1}.base` > ${1}.base.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Loop over all config files, validating and perhaps restoring each one
|
||||||
|
for config_name in ${config_names}; do
|
||||||
|
config=${ETC}/${config_name}
|
||||||
|
|
||||||
|
# skip any that are not in the read-only FS (e.g. shotmanager on controller)
|
||||||
|
if [ ! -f ${ROFS}/etc/${config_name}.orig ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
dmsg ""
|
||||||
|
dmsg "${config_name}... "
|
||||||
|
|
||||||
|
if valid ${config}.conf; then
|
||||||
|
dmsg "${config}.conf is valid"
|
||||||
|
|
||||||
|
if upgraded ${config}; then
|
||||||
|
kmsg -n "merging ${config}... "
|
||||||
|
dmsg ""
|
||||||
|
# typical just after an upgrade if .orig file changes
|
||||||
|
do_merge ${config}
|
||||||
|
else
|
||||||
|
# typical startup
|
||||||
|
dmsg "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif valid ${config}.back; then
|
||||||
|
dmsg "${config}.back is valid"
|
||||||
|
|
||||||
|
# previous shutdown corrupted the .conf file
|
||||||
|
kmsg -n "restoring ${config}.conf... "
|
||||||
|
dmsg ""
|
||||||
|
do_restore ${config}.back ${config}.conf
|
||||||
|
if upgraded ${config}; then
|
||||||
|
kmsg -n "merging ${config}... "
|
||||||
|
dmsg ""
|
||||||
|
# edge case: previous shutdown prepared update and corrupted .conf file
|
||||||
|
do_merge ${config}
|
||||||
|
else
|
||||||
|
dmsg "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
dmsg "neither ${config}.conf nor ${config}.back is valid"
|
||||||
|
|
||||||
|
# typical on first boot
|
||||||
|
kmsg -n "initializing ${config}.conf... "
|
||||||
|
dmsg ""
|
||||||
|
if [ ! -f ${config}.orig ]; then
|
||||||
|
kmsg -n "${config}.orig not found; retrieving from ${ROFS}/etc/${config_name}.orig... "
|
||||||
|
dmsg ""
|
||||||
|
cp ${ROFS}/etc/${config_name}.orig ${config}.orig
|
||||||
|
fi
|
||||||
|
dmsg "copy ${config}.orig -> ${config}.base"
|
||||||
|
cp ${config}.orig ${config}.base
|
||||||
|
md5sum `realpath ${config}.base` > ${config}.base.md5
|
||||||
|
do_restore ${config}.orig ${config}.conf
|
||||||
|
md5sum `realpath ${config}.orig` > ${config}.orig.md5
|
||||||
|
dmsg "done"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
dmsg ""
|
||||||
|
kmsg "OK"
|
23
sololink/config/logrotate-sololink.conf.controller
Normal file
23
sololink/config/logrotate-sololink.conf.controller
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# see "man logrotate" for details
|
||||||
|
# rotate logs, keeping 20 versions
|
||||||
|
|
||||||
|
# specifying 'create' makes it so the log file is always there, even if
|
||||||
|
# nothing writes to it on a particular run, so on the next rotation, the empty
|
||||||
|
# file is rotated along with all the others, keeping the .N extensions in sync
|
||||||
|
# (e.g. so all the .2 files are from the same run).
|
||||||
|
|
||||||
|
# specifying 'missingok' is needed for the very first run when there are no
|
||||||
|
# log files to rotate.
|
||||||
|
|
||||||
|
create
|
||||||
|
missingok
|
||||||
|
rotate 20
|
||||||
|
nodateext
|
||||||
|
|
||||||
|
/log/3dr-top.log { }
|
||||||
|
/log/3dr-solo.log { }
|
||||||
|
/log/3dr-stm32.log { }
|
||||||
|
/log/3dr-video.log { }
|
||||||
|
/log/3dr-wifi.log { }
|
||||||
|
/log/3dr-temp.log { }
|
||||||
|
/log/pkt_delays.csv { }
|
25
sololink/config/logrotate-sololink.conf.solo
Normal file
25
sololink/config/logrotate-sololink.conf.solo
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# see "man logrotate" for details
|
||||||
|
# rotate logs, keeping 20 versions
|
||||||
|
|
||||||
|
# specifying 'create' makes it so the log file is always there, even if
|
||||||
|
# nothing writes to it on a particular run, so on the next rotation, the empty
|
||||||
|
# file is rotated along with all the others, keeping the .N extensions in sync
|
||||||
|
# (e.g. so all the .2 files are from the same run).
|
||||||
|
|
||||||
|
# specifying 'missingok' is needed for the very first run when there are no
|
||||||
|
# log files to rotate.
|
||||||
|
|
||||||
|
create
|
||||||
|
missingok
|
||||||
|
rotate 20
|
||||||
|
nodateext
|
||||||
|
|
||||||
|
/log/3dr-top.log { }
|
||||||
|
/log/3dr-solo.log { }
|
||||||
|
/log/3dr-rc.log { }
|
||||||
|
/log/3dr-telem.log { }
|
||||||
|
/log/3dr-wifi.log { }
|
||||||
|
/log/3dr-video.log { }
|
||||||
|
/log/3dr-temp.log { }
|
||||||
|
/log/shotlog.log { }
|
||||||
|
/log/3dr-accessorykit.log { }
|
3
sololink/config/max_dgram_qlen
Executable file
3
sololink/config/max_dgram_qlen
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
echo "100" > /proc/sys/net/unix/max_dgram_qlen
|
||||||
|
echo "max_dgram_qlen=`cat /proc/sys/net/unix/max_dgram_qlen`"
|
5
sololink/config/sololink
Normal file
5
sololink/config/sololink
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# This file goes in /etc/profile.d/ and is sourced by /etc/profile at boot time
|
||||||
|
# Contents are created by sololink recipe, and should end up looking something
|
||||||
|
# like this:
|
||||||
|
# export SOLOLINK_CONFIG_DIR=/etc
|
||||||
|
# export SOLOLINK_LOG_DIR=/log
|
191
sololink/config/sololink.orig
Normal file
191
sololink/config/sololink.orig
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
[solo]
|
||||||
|
|
||||||
|
artooIp=10.1.1.1
|
||||||
|
|
||||||
|
soloIp=10.1.1.10
|
||||||
|
|
||||||
|
# Address to which RC packets are sent
|
||||||
|
rcDestPort=5005
|
||||||
|
|
||||||
|
# Address to which 'sysinfo' packet for the STM32 is sent
|
||||||
|
sysDestIp=%(artooIp)s
|
||||||
|
sysDestPort=5012
|
||||||
|
|
||||||
|
# Port to which 'pair request' packet for the STM32 is sent
|
||||||
|
pairReqDestPort=5013
|
||||||
|
|
||||||
|
# Port to which 'pair result' packet for the STM32 is sent
|
||||||
|
pairResDestPort=5014
|
||||||
|
|
||||||
|
# Port to which MAVLink packets for the STM32 are sent
|
||||||
|
mavDestPort=5015
|
||||||
|
|
||||||
|
# Port to connect to for button events (TCP)
|
||||||
|
buttonEventPort=5016
|
||||||
|
|
||||||
|
# Port to send button function config messages to
|
||||||
|
buttonFunctionConfigDestPort=5017
|
||||||
|
|
||||||
|
# Port to send set shot info messages to
|
||||||
|
setShotInfoDestPort=5018
|
||||||
|
|
||||||
|
# Port to send updater messages to
|
||||||
|
updaterDestPort=5019
|
||||||
|
|
||||||
|
# Port to which MAVLink packets are sent for all external systems
|
||||||
|
telemDestPort=14550
|
||||||
|
|
||||||
|
# TCP port where app server listens for connections
|
||||||
|
appServerPort=5502
|
||||||
|
|
||||||
|
# File where app_server saves connect app's IP address
|
||||||
|
appAddressFile=/var/run/solo_app.ip
|
||||||
|
|
||||||
|
# Artoo's serial ports
|
||||||
|
|
||||||
|
# Console is /dev/ttymxc0
|
||||||
|
|
||||||
|
# STM32
|
||||||
|
stm32Dev=/dev/ttymxc1
|
||||||
|
stm32Baud=115200
|
||||||
|
|
||||||
|
# Solo's serial ports
|
||||||
|
|
||||||
|
# Console is /dev/ttymxc0
|
||||||
|
|
||||||
|
# Pixhawk telemetry
|
||||||
|
telemDev=/dev/ttymxc1
|
||||||
|
telemBaud=921600
|
||||||
|
telemFlow=True
|
||||||
|
|
||||||
|
# Telemetry logging control
|
||||||
|
telemLogGap=1000000
|
||||||
|
telemLogDelayMax=100000
|
||||||
|
#telemLogDelayFile=/tmp/pkt_delays.csv
|
||||||
|
|
||||||
|
# Pixhawk RC
|
||||||
|
rcDsmDev=/dev/ttymxc2
|
||||||
|
rcDsmBaud=115200
|
||||||
|
|
||||||
|
# IP addresses from which Solo accepts RC packets
|
||||||
|
rcSourceIps=10.1.1.1,127.0.0.1
|
||||||
|
|
||||||
|
# Set system time from GPS when available
|
||||||
|
useGpsTime=True
|
||||||
|
|
||||||
|
# Throttle PWM mapping
|
||||||
|
pwmInMinThrottle=1000
|
||||||
|
pwmInMaxThrottle=2000
|
||||||
|
pwmOutMinThrottle=1000
|
||||||
|
pwmOutMaxThrottle=1900
|
||||||
|
|
||||||
|
# Rc timeout max
|
||||||
|
rcTimeoutUS=400000
|
||||||
|
|
||||||
|
# Telemetry display units (metric or imperial)
|
||||||
|
uiUnits=metric
|
||||||
|
|
||||||
|
[pairing]
|
||||||
|
user_confirmation_timeout = 30.0
|
||||||
|
controller_link_port = 5501
|
||||||
|
wifi_connect_timeout = 5.0
|
||||||
|
connect_request_interval = 1.0
|
||||||
|
connect_ack_timeout = 0.5
|
||||||
|
solo_address_file = /var/run/solo.ip
|
||||||
|
button_filename = /dev/input/event0
|
||||||
|
|
||||||
|
[net]
|
||||||
|
ApEnable=True
|
||||||
|
StationEnable=False
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys=root,stm32,pix,pair,net,app,tlm,shot
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys=consoleHandler,sysLogHandler,sysLog2Handler
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys=simpleFormatter,syslogFormatter
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level=INFO
|
||||||
|
handlers=consoleHandler
|
||||||
|
|
||||||
|
[logger_stm32]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=stm32
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_pix]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=pix
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_pair]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=pair
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_net]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=net
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_app]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=app
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_tlm]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLogHandler
|
||||||
|
qualname=tlm
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[logger_shot]
|
||||||
|
level=INFO
|
||||||
|
handlers=sysLog2Handler
|
||||||
|
qualname=shot
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[handler_consoleHandler]
|
||||||
|
class=StreamHandler
|
||||||
|
level=ERROR
|
||||||
|
formatter=simpleFormatter
|
||||||
|
args=(sys.stdout,)
|
||||||
|
|
||||||
|
[handler_sysLogHandler]
|
||||||
|
class=handlers.SysLogHandler
|
||||||
|
level=DEBUG
|
||||||
|
formatter=syslogFormatter
|
||||||
|
args=("/dev/log", handlers.SysLogHandler.LOG_LOCAL1)
|
||||||
|
|
||||||
|
[handler_sysLog2Handler]
|
||||||
|
class=handlers.SysLogHandler
|
||||||
|
level=DEBUG
|
||||||
|
formatter=syslogFormatter
|
||||||
|
args=("/dev/log", handlers.SysLogHandler.LOG_LOCAL2)
|
||||||
|
|
||||||
|
[formatter_simpleFormatter]
|
||||||
|
format=%(asctime)s %(name)-4s %(levelname)-8s %(message)s
|
||||||
|
datefmt=
|
||||||
|
|
||||||
|
[formatter_syslogFormatter]
|
||||||
|
format=%(name)s: %(message)s
|
||||||
|
datefmt=
|
||||||
|
|
||||||
|
[video]
|
||||||
|
videoMinFR=24
|
||||||
|
videoMaxFR=24
|
||||||
|
videoMinBR=800000
|
||||||
|
videoMaxBR=1800000
|
||||||
|
videoFRStep=5
|
||||||
|
videoBRStep=100000
|
||||||
|
varStreamRes=True
|
||||||
|
cropRecordRes=True
|
||||||
|
|
1289
sololink/config/sololink_config
Executable file
1289
sololink/config/sololink_config
Executable file
File diff suppressed because it is too large
Load Diff
28
sololink/config/sololink_config_sample
Executable file
28
sololink/config/sololink_config_sample
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# run_cmd prints a command, then the command's output, then 'ok' or 'error',
|
||||||
|
# depending on the command's exit status, then a blank line.
|
||||||
|
#
|
||||||
|
# All text after the command and before the 'ok' or 'error' is what the
|
||||||
|
# command sends to stdout.
|
||||||
|
run_cmd() {
|
||||||
|
echo $1
|
||||||
|
$1
|
||||||
|
if [ $? -eq 0 ]; then echo "ok"; else echo "error"; fi
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
for SSH in "ssh root@10.1.1.1" "ssh root@10.1.1.10"; do
|
||||||
|
|
||||||
|
run_cmd "$SSH sololink_config --get-version"
|
||||||
|
run_cmd "$SSH sololink_config --get-version sololink"
|
||||||
|
run_cmd "$SSH sololink_config --get-version artoo"
|
||||||
|
run_cmd "$SSH sololink_config --get-version pixhawk"
|
||||||
|
run_cmd "$SSH sololink_config --get-version golden"
|
||||||
|
run_cmd "$SSH sololink_config --get-version all"
|
||||||
|
run_cmd "$SSH sololink_config --get-wifi-ssid"
|
||||||
|
run_cmd "$SSH sololink_config --get-wifi-password"
|
||||||
|
run_cmd "$SSH sololink_config --get-image"
|
||||||
|
run_cmd "$SSH sololink_config --get-pairing"
|
||||||
|
|
||||||
|
done
|
689
sololink/config/sololink_config_test
Executable file
689
sololink/config/sololink_config_test
Executable file
@ -0,0 +1,689 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# runs on ubuntu-14.04
|
||||||
|
# does not run on osx
|
||||||
|
|
||||||
|
if [ -z ${TESTHOSTNAME} ]; then
|
||||||
|
echo "ERROR: set TESTHOSTNAME"
|
||||||
|
echo " TESTHOSTNAME=3dr_solo ./sololink_config_test"
|
||||||
|
echo " TESTHOSTNAME=3dr_controller ./sololink_config_test"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export SOLOLINK_CONFIG_TEST=1
|
||||||
|
export ROOT=./test_${TESTHOSTNAME}
|
||||||
|
|
||||||
|
error=0
|
||||||
|
|
||||||
|
if ! ./sololink_config --pass; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ./sololink_config --fail; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
### WIFI SSID
|
||||||
|
|
||||||
|
# backup, ignoring errors
|
||||||
|
cp ${ROOT}/etc/wpa_supplicant.conf wpa_supplicant.save > /dev/null 2>&1
|
||||||
|
cp ${ROOT}/etc/hostapd.conf hostapd.save > /dev/null 2>&1
|
||||||
|
|
||||||
|
# get to initial state
|
||||||
|
./sololink_config --reset-wifi-settings
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; not set ... "
|
||||||
|
x=`./sololink_config --set-wifi-ssid SoloLink_one`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "SoloLink_one" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; already set ... "
|
||||||
|
x=`./sololink_config --set-wifi-ssid SoloLink_two`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "SoloLink_two" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; too short ... "
|
||||||
|
x=`./sololink_config --set-wifi-ssid 1234567`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; not SoloLink_ ... "
|
||||||
|
x=`./sololink_config --set-wifi-ssid 12345678`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; max length ... "
|
||||||
|
xx="SoloLink_12345678901234567890123"
|
||||||
|
x=`./sololink_config --set-wifi-ssid $xx`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "$xx" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set ssid; too long ... "
|
||||||
|
x=`./sololink_config --set-wifi-ssid SoloLink_123456789012345678901234`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# restore
|
||||||
|
if [ -f wpa_supplicant.save ]; then
|
||||||
|
mv wpa_supplicant.save ${ROOT}/etc/wpa_supplicant.conf
|
||||||
|
fi
|
||||||
|
if [ -f hostapd.save ]; then
|
||||||
|
mv hostapd.save ${ROOT}/etc/hostapd.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
### WIFI PASSWORD
|
||||||
|
|
||||||
|
# backup, ignoring errors
|
||||||
|
cp ${ROOT}/etc/wpa_supplicant.conf wpa_supplicant.save > /dev/null 2>&1
|
||||||
|
cp ${ROOT}/etc/hostapd.conf hostapd.save > /dev/null 2>&1
|
||||||
|
|
||||||
|
# get to initial state
|
||||||
|
./sololink_config --reset-wifi-settings
|
||||||
|
|
||||||
|
echo -n "wifi: set password; not set ... "
|
||||||
|
x=`./sololink_config --set-wifi-password 12345678`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "12345678" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set password; already set ... "
|
||||||
|
x=`./sololink_config --set-wifi-password 87654321`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "87654321" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set password; with space ... "
|
||||||
|
x=`./sololink_config --set-wifi-password "ABCD EFGH"`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "ABCD EFGH" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set password; max length ... "
|
||||||
|
xx=123456789012345678901234567890123456789012345678901234567890123
|
||||||
|
x=`./sololink_config --set-wifi-password $xx`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "$xx" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set password; too short ... "
|
||||||
|
x=`./sololink_config --set-wifi-password 1234567`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "wifi: set password; too long ... "
|
||||||
|
xx=1234567890123456789012345678901234567890123456789012345678901234
|
||||||
|
x=`./sololink_config --set-wifi-password $xx`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# restore
|
||||||
|
if [ -f wpa_supplicant.save ]; then
|
||||||
|
mv wpa_supplicant.save ${ROOT}/etc/wpa_supplicant.conf
|
||||||
|
fi
|
||||||
|
if [ -f hostapd.save ]; then
|
||||||
|
mv hostapd.save ${ROOT}/etc/hostapd.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
### PAIR/UNPAIR
|
||||||
|
|
||||||
|
# backup, ignoring errors
|
||||||
|
cp ${ROOT}/etc/wpa_supplicant.conf wpa_supplicant.save > /dev/null 2>&1
|
||||||
|
cp ${ROOT}/log/3dr-pairing.conf 3dr-pairing.save > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo -n "pair: set pairing; unpair ... "
|
||||||
|
x=`./sololink_config --set-pairing`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if grep -q "network=" ${ROOT}/etc/wpa_supplicant.conf; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ -f ${ROOT}/log/3dr-pairing.conf ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "pair: get pairing; when unpaired ... "
|
||||||
|
pair=`./sololink_config --get-pairing`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "pair: set pairing; not paired ... "
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
peer="SoloLink_ABC"
|
||||||
|
elif [ "${TESTHOSTNAME}" == "3dr_controller" ]; then
|
||||||
|
peer="12:34:56:78:90:ab"
|
||||||
|
fi
|
||||||
|
pair=`./sololink_config --set-pairing ${peer}`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if ! grep -q "ssid=\"${peer}\"" ${ROOT}/etc/wpa_supplicant.conf; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
cat ${ROOT}/etc/wpa_supplicant.conf
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ! -f ${ROOT}/log/3dr-pairing.conf ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "pair: set pairing; already paired ... "
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
peer="SoloLink_123"
|
||||||
|
elif [ "${TESTHOSTNAME}" == "3dr_controller" ]; then
|
||||||
|
peer="cd:EF:ab:11:22:9A"
|
||||||
|
fi
|
||||||
|
x=`./sololink_config --set-pairing ${peer}`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if ! grep -q "ssid=\"${peer}\"" ${ROOT}/etc/wpa_supplicant.conf; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
cat ${ROOT}/etc/wpa_supplicant.conf
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ! -f ${ROOT}/log/3dr-pairing.conf ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# restore
|
||||||
|
if [ -f wpa_supplicant.save ]; then
|
||||||
|
mv wpa_supplicant.save ${ROOT}/etc/wpa_supplicant.conf
|
||||||
|
fi
|
||||||
|
if [ -f 3dr-pairing.save ]; then
|
||||||
|
mv 3dr-pairing.save ${ROOT}/log/3dr-pairing.conf
|
||||||
|
else
|
||||||
|
rm -f ${ROOT}/log/3dr-pairing.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
### UPDATE
|
||||||
|
|
||||||
|
echo -n "update: factory reset ... "
|
||||||
|
x=`./sololink_config --factory-reset`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ ! -f ${ROOT}/log/updates/FACTORYRESET ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: settings reset ... "
|
||||||
|
x=`./sololink_config --settings-reset`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ ! -f ${ROOT}/log/updates/RESETSETTINGS ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir_empty() {
|
||||||
|
[ -d ${1} ] && [ `find ${1} | wc -l` -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -n "update: prepare artoo ... "
|
||||||
|
x=`./sololink_config --update-prepare artoo`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif ! dir_empty ${ROOT}/firmware; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: apply artoo ... "
|
||||||
|
x=`./sololink_config --update-apply artoo`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: prepare pixhawk ... "
|
||||||
|
x=`./sololink_config --update-prepare pixhawk`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif ! dir_empty ${ROOT}/firmware; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: apply pixhawk ... "
|
||||||
|
x=`./sololink_config --update-apply pixhawk`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: prepare sololink ... "
|
||||||
|
x=`./sololink_config --update-prepare sololink`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif ! dir_empty ${ROOT}/log/updates; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "update: apply sololink; correct file ... "
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
updatefile="3dr-solo-1234.tar.gz"
|
||||||
|
else
|
||||||
|
updatefile="3dr-controller-1234.tar.gz"
|
||||||
|
fi
|
||||||
|
touch ${ROOT}/log/updates/${updatefile}
|
||||||
|
pushd ${ROOT}/log/updates > /dev/null 2>&1
|
||||||
|
md5sum ${updatefile} > ${updatefile}.md5
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
x=`./sololink_config --update-apply sololink`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ ! -f ${ROOT}/log/updates/UPDATE ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
rm ${ROOT}/log/updates/*
|
||||||
|
|
||||||
|
echo -n "update: apply sololink; incorrect file ... "
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
updatefile="3dr-controller-1234.tar.gz"
|
||||||
|
else
|
||||||
|
updatefile="3dr-solo-1234.tar.gz"
|
||||||
|
fi
|
||||||
|
touch ${ROOT}/log/updates/${updatefile}
|
||||||
|
pushd ${ROOT}/log/updates > /dev/null 2>&1
|
||||||
|
md5sum ${updatefile} > ${updatefile}.md5
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
x=`./sololink_config --update-apply sololink`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
rm ${ROOT}/log/updates/${updatefile}*
|
||||||
|
|
||||||
|
echo -n "update: apply sololink; bad md5 ... "
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
updatefile="3dr-solo-1234.tar.gz"
|
||||||
|
else
|
||||||
|
updatefile="3dr-controller-1234.tar.gz"
|
||||||
|
fi
|
||||||
|
touch ${ROOT}/log/updates/${updatefile}
|
||||||
|
pushd ${ROOT}/log/updates > /dev/null 2>&1
|
||||||
|
md5sum ${updatefile} > ${updatefile}.md5
|
||||||
|
md5sum ${updatefile}.md5 > ${updatefile}.md5
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
x=`./sololink_config --update-apply sololink`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
rm ${ROOT}/log/updates/${updatefile}*
|
||||||
|
|
||||||
|
echo -n "update: apply sololink; missing file ... "
|
||||||
|
x=`./sololink_config --update-apply sololink`
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
### VERSION
|
||||||
|
|
||||||
|
echo -n "version: script ... "
|
||||||
|
x=`./sololink_config --get-version`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "version: sololink ... "
|
||||||
|
x=`./sololink_config --get-version sololink`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}: ${x}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "version: artoo ... "
|
||||||
|
x=`./sololink_config --get-version artoo`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}: ${x}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}: ${x}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "version: pixhawk ... "
|
||||||
|
x=`./sololink_config --get-version pixhawk`
|
||||||
|
r=$?
|
||||||
|
if [ "${TESTHOSTNAME}" == "3dr_solo" ]; then
|
||||||
|
if [ ${r} -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}: ${x}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ${r} -eq 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}: ${x}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "${x} ... OK"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
### GET/SET CONFIG
|
||||||
|
|
||||||
|
# backup, ignoring errors
|
||||||
|
cp ${ROOT}/etc/sololink.conf sololink.save > /dev/null 2>&1
|
||||||
|
cp ${ROOT}/etc/sololink.conf.md5 sololink.save.md5 > /dev/null 2>&1
|
||||||
|
|
||||||
|
# append test variables
|
||||||
|
cat >> ${ROOT}/etc/sololink.conf <<TESTVARS
|
||||||
|
var0=val0
|
||||||
|
var1=val1
|
||||||
|
var2 =val2
|
||||||
|
var3= val3
|
||||||
|
var4 = val4
|
||||||
|
var5=val5
|
||||||
|
var6 =val6
|
||||||
|
var7= val7
|
||||||
|
var8 = val8
|
||||||
|
var9 = val9
|
||||||
|
TESTVARS
|
||||||
|
|
||||||
|
check_get_config() {
|
||||||
|
echo -n "get-config: ${1}=${2} ... "
|
||||||
|
x=`./sololink_config --get-config ${1}`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "${2}" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_set_config() {
|
||||||
|
echo -n "set-config: ${1}=${2} ... "
|
||||||
|
x=`./sololink_config --set-config ${1} ${2}`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "${2}" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# get, does not exist
|
||||||
|
echo -n "get-config: get nonexistant variable ... "
|
||||||
|
x=`./sololink_config --get-config qwerty`
|
||||||
|
if [ $? -ne 1 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "qwerty not set" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set, does not exist
|
||||||
|
echo -n "set-config: set nonexistant variable ... "
|
||||||
|
x=`./sololink_config --set-config qwerty asdf`
|
||||||
|
if [ $? -ne 1 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "qwerty not set" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set, does not exist, add new
|
||||||
|
echo -n "set-config: set nonexistant variable with add ... "
|
||||||
|
x=`./sololink_config --set-config qwerty asdf --add`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
elif [ "$x" != "asdf" ]; then
|
||||||
|
echo "ERROR at line ${LINENO}"
|
||||||
|
error=1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get
|
||||||
|
check_get_config var0 val0
|
||||||
|
check_get_config var1 val1
|
||||||
|
check_get_config var2 val2
|
||||||
|
check_get_config var3 val3
|
||||||
|
check_get_config var4 val4
|
||||||
|
check_get_config var5 val5
|
||||||
|
check_get_config var6 val6
|
||||||
|
check_get_config var7 val7
|
||||||
|
check_get_config var8 val8
|
||||||
|
check_get_config var9 val9
|
||||||
|
|
||||||
|
# set
|
||||||
|
check_set_config var0 new0
|
||||||
|
check_set_config var1 new1
|
||||||
|
check_set_config var2 new2
|
||||||
|
check_set_config var3 new3
|
||||||
|
check_set_config var4 new4
|
||||||
|
check_set_config var5 new5
|
||||||
|
check_set_config var6 new6
|
||||||
|
check_set_config var7 new7
|
||||||
|
check_set_config var8 new8
|
||||||
|
check_set_config var9 new9
|
||||||
|
|
||||||
|
# get
|
||||||
|
check_get_config var0 new0
|
||||||
|
check_get_config var1 new1
|
||||||
|
check_get_config var2 new2
|
||||||
|
check_get_config var3 new3
|
||||||
|
check_get_config var4 new4
|
||||||
|
check_get_config var5 new5
|
||||||
|
check_get_config var6 new6
|
||||||
|
check_get_config var7 new7
|
||||||
|
check_get_config var8 new8
|
||||||
|
check_get_config var9 new9
|
||||||
|
|
||||||
|
# restore
|
||||||
|
if [ -f sololink.save ]; then
|
||||||
|
mv sololink.save ${ROOT}/etc/sololink.conf
|
||||||
|
fi
|
||||||
|
if [ -f sololink.save.md5 ]; then
|
||||||
|
mv sololink.save.md5 ${ROOT}/etc/sololink.conf.md5
|
||||||
|
fi
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
if [ $error -eq 0 ]; then
|
||||||
|
echo ALL PASSED
|
||||||
|
else
|
||||||
|
echo ERRORS
|
||||||
|
fi
|
9
sololink/config/syslog.conf.busybox.controller
Normal file
9
sololink/config/syslog.conf.busybox.controller
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Configuration file for busybox's syslogd utility
|
||||||
|
local0.info /log/3dr-stm32.log
|
||||||
|
local1.info /log/3dr-solo.log
|
||||||
|
local2.info /log/3dr-wifi.log
|
||||||
|
local3.info /log/3dr-video.log
|
||||||
|
|
||||||
|
local5.info /log/3dr-temp.log
|
||||||
|
|
||||||
|
kern,user.* /log/kern.log
|
11
sololink/config/syslog.conf.busybox.solo
Normal file
11
sololink/config/syslog.conf.busybox.solo
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Configuration file for busybox's syslogd utility
|
||||||
|
local0.info /log/3dr-rc.log
|
||||||
|
local1.info /log/3dr-solo.log
|
||||||
|
local2.info /log/shotlog.log
|
||||||
|
local3.info /log/3dr-wifi.log
|
||||||
|
local4.info /log/3dr-telem.log
|
||||||
|
local5.info /log/3dr-temp.log
|
||||||
|
local6.info /log/3dr-video.log
|
||||||
|
local7.info /log/3dr-accessorykit.log
|
||||||
|
|
||||||
|
kern,user.* /log/kern.log
|
2
sololink/config/test_3dr_README
Normal file
2
sololink/config/test_3dr_README
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
The directories test_3dr_controller and test_3dr_solo are used by the test
|
||||||
|
script sololink_config_test.
|
1
sololink/config/test_3dr_controller/STM_VERSION
Normal file
1
sololink/config/test_3dr_controller/STM_VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.6.9
|
1
sololink/config/test_3dr_controller/VERSION
Normal file
1
sololink/config/test_3dr_controller/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.6.5
|
1749
sololink/config/test_3dr_controller/etc/hostapd.conf
Normal file
1749
sololink/config/test_3dr_controller/etc/hostapd.conf
Normal file
File diff suppressed because it is too large
Load Diff
1
sololink/config/test_3dr_controller/etc/hostapd.conf.md5
Normal file
1
sololink/config/test_3dr_controller/etc/hostapd.conf.md5
Normal file
@ -0,0 +1 @@
|
|||||||
|
a46c206ffafef83c91cebf497ea785b5 ./test_3dr_controller/etc/hostapd.conf
|
1749
sololink/config/test_3dr_controller/mnt/rootfs.ro/etc/hostapd.orig
Normal file
1749
sololink/config/test_3dr_controller/mnt/rootfs.ro/etc/hostapd.orig
Normal file
File diff suppressed because it is too large
Load Diff
1
sololink/config/test_3dr_solo/PIX_VERSION
Normal file
1
sololink/config/test_3dr_solo/PIX_VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.0.28
|
1
sololink/config/test_3dr_solo/VERSION
Normal file
1
sololink/config/test_3dr_solo/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.6.5
|
6
sololink/config/test_3dr_solo/etc/wpa_supplicant.conf
Normal file
6
sololink/config/test_3dr_solo/etc/wpa_supplicant.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
ctrl_interface=/var/run/wpa_supplicant
|
||||||
|
ctrl_interface_group=0
|
||||||
|
update_config=1
|
||||||
|
device_name=Solo
|
||||||
|
manufacturer=3D Robotics
|
||||||
|
model_name=Solo
|
@ -0,0 +1 @@
|
|||||||
|
132d9621948cfb385c5975b51dec4758 ./test_3dr_solo/etc/wpa_supplicant.conf
|
@ -0,0 +1,6 @@
|
|||||||
|
ctrl_interface=/var/run/wpa_supplicant
|
||||||
|
ctrl_interface_group=0
|
||||||
|
update_config=1
|
||||||
|
device_name=Solo
|
||||||
|
manufacturer=3D Robotics
|
||||||
|
model_name=Solo
|
244
sololink/config/test_configinit
Executable file
244
sololink/config/test_configinit
Executable file
@ -0,0 +1,244 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
verbose=
|
||||||
|
|
||||||
|
# arguments
|
||||||
|
# -v verbose
|
||||||
|
until [ -z "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-v) verbose="-v" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# This runs on Ubuntu
|
||||||
|
MD5SUM='md5sum -c --status'
|
||||||
|
|
||||||
|
config_names="hostapd wpa_supplicant sololink"
|
||||||
|
|
||||||
|
valid() {
|
||||||
|
[ -f ${1} ] && [ -f ${1}.md5 ] && ${MD5SUM} ${1}.md5
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_valid() {
|
||||||
|
if ! valid ${1}; then
|
||||||
|
echo "##### TEST FAILED ##### (valid ${1})"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_same() {
|
||||||
|
if ! cmp ${1} ${2}; then
|
||||||
|
echo "##### TEST FAILED ##### (cmp ${1} ${2})"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_first_boot() {
|
||||||
|
echo
|
||||||
|
echo "##### test_first_boot starting"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# delete all
|
||||||
|
mkdir -p etc/
|
||||||
|
rm -f etc/*
|
||||||
|
|
||||||
|
# only .orig files exist
|
||||||
|
for config in ${config_names}; do
|
||||||
|
cp rootfs.ro/etc/${config}.orig etc/${config}.orig
|
||||||
|
chmod +w etc/${config}.orig
|
||||||
|
done
|
||||||
|
|
||||||
|
./configinit ${verbose}
|
||||||
|
|
||||||
|
# should have:
|
||||||
|
# * .base, .base.md5
|
||||||
|
# * .conf, .conf.md5
|
||||||
|
# * .orig, .orig.md5
|
||||||
|
# * .base, .conf, .orig should all be the same
|
||||||
|
# * all .md5 should be good
|
||||||
|
for config in ${config_names}; do
|
||||||
|
assert_valid etc/${config}.base
|
||||||
|
assert_valid etc/${config}.conf
|
||||||
|
assert_valid etc/${config}.orig
|
||||||
|
assert_same etc/${config}.orig etc/${config}.base
|
||||||
|
assert_same etc/${config}.orig etc/${config}.conf
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "##### test_first_boot complete"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
test_normal_boot() {
|
||||||
|
echo
|
||||||
|
echo "##### test_normal_boot starting"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# delete all
|
||||||
|
mkdir -p etc/
|
||||||
|
rm -f etc/*
|
||||||
|
|
||||||
|
# .orig, .base, and .conf files exist
|
||||||
|
for config in ${config_names}; do
|
||||||
|
cp rootfs.ro/etc/${config}.orig etc/${config}.orig
|
||||||
|
chmod +w etc/${config}.orig
|
||||||
|
md5sum `realpath etc/${config}.orig` > etc/${config}.orig.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.base
|
||||||
|
md5sum `realpath etc/${config}.base` > etc/${config}.base.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.conf
|
||||||
|
# .conf is normally changed from .orig
|
||||||
|
echo "# added line!" >> etc/${config}.conf
|
||||||
|
md5sum `realpath etc/${config}.conf` > etc/${config}.conf.md5
|
||||||
|
done
|
||||||
|
|
||||||
|
./configinit ${verbose}
|
||||||
|
|
||||||
|
# should have:
|
||||||
|
# * .base, .base.md5
|
||||||
|
# * .conf, .conf.md5
|
||||||
|
# * .orig, .orig.md5
|
||||||
|
# * .base and .orig should be the same
|
||||||
|
# * all .md5 should be good
|
||||||
|
for config in ${config_names}; do
|
||||||
|
assert_valid etc/${config}.base
|
||||||
|
assert_valid etc/${config}.conf
|
||||||
|
assert_valid etc/${config}.orig
|
||||||
|
assert_same etc/${config}.orig etc/${config}.base
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "##### test_normal_boot complete"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
test_invalid_conf() {
|
||||||
|
echo
|
||||||
|
echo "##### test_invalid_conf starting"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# delete all
|
||||||
|
mkdir -p etc/
|
||||||
|
rm -f etc/*
|
||||||
|
|
||||||
|
# .orig, .base, and .conf files exist
|
||||||
|
for config in ${config_names}; do
|
||||||
|
cp rootfs.ro/etc/${config}.orig etc/${config}.orig
|
||||||
|
chmod +w etc/${config}.orig
|
||||||
|
md5sum `realpath etc/${config}.orig` > etc/${config}.orig.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.base
|
||||||
|
md5sum `realpath etc/${config}.base` > etc/${config}.base.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.conf
|
||||||
|
# incomplete update of .conf
|
||||||
|
cp etc/${config}.conf etc/${config}.back
|
||||||
|
md5sum `realpath etc/${config}.back` > etc/${config}.back.md5
|
||||||
|
echo "# added line!" >> etc/${config}.conf
|
||||||
|
# .conf.md5 is now invalid
|
||||||
|
done
|
||||||
|
|
||||||
|
./configinit ${verbose}
|
||||||
|
|
||||||
|
# should have:
|
||||||
|
# * .base, .base.md5
|
||||||
|
# * .conf, .conf.md5
|
||||||
|
# * .orig, .orig.md5
|
||||||
|
# * .base and .orig should be the same
|
||||||
|
# * all .md5 should be good
|
||||||
|
for config in ${config_names}; do
|
||||||
|
assert_valid etc/${config}.base
|
||||||
|
assert_valid etc/${config}.conf
|
||||||
|
assert_valid etc/${config}.orig
|
||||||
|
assert_same etc/${config}.orig etc/${config}.base
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "##### test_invalid_conf complete"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
test_update() {
|
||||||
|
echo
|
||||||
|
echo "##### test_update starting"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# delete all
|
||||||
|
mkdir -p etc/
|
||||||
|
rm -f etc/*
|
||||||
|
|
||||||
|
# .orig, .base, and .conf files exist
|
||||||
|
for config in ${config_names}; do
|
||||||
|
cp rootfs.ro/etc/${config}.orig etc/${config}.orig
|
||||||
|
chmod +w etc/${config}.orig
|
||||||
|
md5sum `realpath etc/${config}.orig` > etc/${config}.orig.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.base
|
||||||
|
md5sum `realpath etc/${config}.base` > etc/${config}.base.md5
|
||||||
|
cp etc/${config}.orig etc/${config}.conf
|
||||||
|
md5sum `realpath etc/${config}.conf` > etc/${config}.conf.md5
|
||||||
|
done
|
||||||
|
|
||||||
|
# prior to the update, hostapd.conf and wpa_supplicant.conf had ssid changed
|
||||||
|
|
||||||
|
sed -i "s/^ssid=SoloLink_.*/ssid=SoloLink_Test/" etc/hostapd.conf
|
||||||
|
md5sum `realpath etc/hostapd.conf` > etc/hostapd.conf.md5
|
||||||
|
|
||||||
|
echo "network={" >> etc/wpa_supplicant.conf
|
||||||
|
echo " ssid=\"SoloLink_Test\"" >> etc/wpa_supplicant.conf
|
||||||
|
echo " key_mgmt=NONE" >> etc/wpa_supplicant.conf
|
||||||
|
echo "}" >> etc/wpa_supplicant.conf
|
||||||
|
md5sum `realpath etc/wpa_supplicant.conf` > etc/wpa_supplicant.conf.md5
|
||||||
|
|
||||||
|
# telemetry baud was changed in sololink.conf
|
||||||
|
sed -i "s/^telemBaud=.*/telemBaud=115200/" etc/sololink.conf
|
||||||
|
md5sum `realpath etc/sololink.conf` > etc/sololink.conf.md5
|
||||||
|
|
||||||
|
# as part of update, we change something in hostapd.orig
|
||||||
|
sed -i "s/^acs_num_scans=.*/acs_num_scans=1000/" etc/hostapd.orig
|
||||||
|
|
||||||
|
# we do not change wpa_supplicant.orig
|
||||||
|
|
||||||
|
# we add a new setting to sololink.orig
|
||||||
|
sed -i "s/useGpsTime=True/useGpsTime=True\n\nuseOuijiTime=Definitely/" etc/sololink.orig
|
||||||
|
|
||||||
|
# .orig.md5 is no longer correct for the updated versions
|
||||||
|
# (that is how the update is detected)
|
||||||
|
|
||||||
|
# show changes
|
||||||
|
for config in ${config_names}; do
|
||||||
|
echo "user changes in ${config}.conf:"
|
||||||
|
diff etc/${config}.base etc/${config}.conf
|
||||||
|
echo
|
||||||
|
echo "update changes in ${config}.orig:"
|
||||||
|
diff rootfs.ro/etc/${config}.orig etc/${config}.orig
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
./configinit ${verbose}
|
||||||
|
|
||||||
|
# should have:
|
||||||
|
# * .base, .base.md5
|
||||||
|
# * .conf, .conf.md5
|
||||||
|
# * .orig, .orig.md5
|
||||||
|
# * .base and .orig should be the same, no user changes
|
||||||
|
# * .conf should have both update and user changes
|
||||||
|
for config in ${config_names}; do
|
||||||
|
assert_valid etc/${config}.base
|
||||||
|
assert_valid etc/${config}.conf
|
||||||
|
assert_valid etc/${config}.orig
|
||||||
|
assert_same etc/${config}.orig etc/${config}.base
|
||||||
|
echo "${config}.conf should have both user and update mods:"
|
||||||
|
diff rootfs.ro/etc/${config}.orig etc/${config}.conf
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "##### test_update complete"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
test_first_boot
|
||||||
|
|
||||||
|
test_normal_boot
|
||||||
|
|
||||||
|
test_invalid_conf
|
||||||
|
|
||||||
|
test_update
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
rm -rf ./etc
|
51
sololink/flightcode/Makefile
Normal file
51
sololink/flightcode/Makefile
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
SUBDIRS =
|
||||||
|
SUBDIRS += arp_table
|
||||||
|
SUBDIRS += dflog
|
||||||
|
SUBDIRS += dataflash_logger
|
||||||
|
SUBDIRS += hostapd_ctrl
|
||||||
|
SUBDIRS += pixrc
|
||||||
|
SUBDIRS += proc_top
|
||||||
|
SUBDIRS += rssi
|
||||||
|
SUBDIRS += stm32
|
||||||
|
SUBDIRS += telem
|
||||||
|
SUBDIRS += telem_ctrl
|
||||||
|
SUBDIRS += tlog
|
||||||
|
SUBDIRS += video/vid
|
||||||
|
SUBDIRS += video/app
|
||||||
|
SUBDIRS += video/hdmi
|
||||||
|
SUBDIRS += unlock
|
||||||
|
SUBDIRS += wdog
|
||||||
|
|
||||||
|
SUBDIRS_BUILD = $(SUBDIRS:%=%_build)
|
||||||
|
SUBDIRS_CLEAN = $(SUBDIRS:%=%_clean)
|
||||||
|
|
||||||
|
# SUBDIRS2 is SUBDIRS, plus directories that can be formatted but not built
|
||||||
|
SUBDIRS2 = $(SUBDIRS)
|
||||||
|
SUBDIRS2 += ini
|
||||||
|
SUBDIRS2 += ini/cpp
|
||||||
|
SUBDIRS2 += log
|
||||||
|
SUBDIRS2 += util
|
||||||
|
|
||||||
|
SUBDIRS_FMT = $(SUBDIRS2:%=%_fmt)
|
||||||
|
SUBDIRS_FMT_DIFF = $(SUBDIRS2:%=%_fmt-diff)
|
||||||
|
|
||||||
|
all: $(SUBDIRS_BUILD)
|
||||||
|
|
||||||
|
build: $(SUBDIRS_BUILD)
|
||||||
|
$(SUBDIRS_BUILD):
|
||||||
|
$(MAKE) -C $(@:%_build=%)
|
||||||
|
|
||||||
|
clean: $(SUBDIRS_CLEAN)
|
||||||
|
$(SUBDIRS_CLEAN):
|
||||||
|
$(MAKE) -C $(@:%_clean=%) clean
|
||||||
|
|
||||||
|
fmt: $(SUBDIRS_FMT)
|
||||||
|
$(SUBDIRS_FMT):
|
||||||
|
$(MAKE) -C $(@:%_fmt=%) fmt
|
||||||
|
|
||||||
|
fmt-diff: $(SUBDIRS_FMT_DIFF)
|
||||||
|
$(SUBDIRS_FMT_DIFF):
|
||||||
|
$(MAKE) -C $(@:%_fmt-diff=%) fmt-diff
|
||||||
|
|
||||||
|
.PHONY: $(SUBDIRS) $(SUBDIRS_BUILD) $(SUBDIRS_CLEAN) $(SUBDIRS_FMT) $(SUBDIRS_FMT_DIFF)
|
40
sololink/flightcode/arp_table/Makefile
Normal file
40
sololink/flightcode/arp_table/Makefile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
VPATH = ../util
|
||||||
|
|
||||||
|
INCS = -I../util
|
||||||
|
|
||||||
|
CFLAGS += -Wall $(INCS)
|
||||||
|
|
||||||
|
SRCS_C = main.c arp_table.c util.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_C:.c=.o)
|
||||||
|
|
||||||
|
MAIN = arp_table
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.c) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
33
sololink/flightcode/arp_table/main.c
Normal file
33
sololink/flightcode/arp_table/main.c
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "arp_table.h"
|
||||||
|
|
||||||
|
#define MAX_ARP_ENTRIES 10
|
||||||
|
arp_entry_t arp_table[MAX_ARP_ENTRIES];
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int arp_entries;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
arp_entries = MAX_ARP_ENTRIES;
|
||||||
|
if (arp_table_get(arp_table, &arp_entries) != 0) {
|
||||||
|
printf("ERROR reading arp table\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < arp_entries; i++) {
|
||||||
|
struct in_addr in;
|
||||||
|
in.s_addr = arp_table[i].ip;
|
||||||
|
uint8_t *mac = arp_table[i].mac;
|
||||||
|
printf("%-15s 0x%x 0x%x %02x:%02x:%02x:%02x:%02x:%02x %s\n", inet_ntoa(in),
|
||||||
|
arp_table[i].hw_type, arp_table[i].flags, mac[0], mac[1], mac[2], mac[3], mac[4],
|
||||||
|
mac[5], arp_table[i].dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
} /* main */
|
37
sololink/flightcode/baudcheck/Makefile
Normal file
37
sololink/flightcode/baudcheck/Makefile
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
VPATH = ../util
|
||||||
|
|
||||||
|
INCS = -I../util
|
||||||
|
|
||||||
|
CFLAGS += -Wall $(INCS)
|
||||||
|
CXXFLAGS += -Wall $(INCS)
|
||||||
|
|
||||||
|
LIBS += -lpthread
|
||||||
|
|
||||||
|
SRCS_CPP = baudcheck.cpp
|
||||||
|
SRCS_C += util.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_CPP:.cpp=.o) $(SRCS_C:.c=.o)
|
||||||
|
|
||||||
|
MAIN = baudcheck
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.cpp) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
$(RM) ../util/*.o
|
||||||
|
|
||||||
|
.PHONY: clean
|
371
sololink/flightcode/baudcheck/baudcheck.cpp
Normal file
371
sololink/flightcode/baudcheck/baudcheck.cpp
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <list>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <poll.h>
|
||||||
|
#include "util.h"
|
||||||
|
#include "../mavlink/c_library/ardupilotmega/mavlink.h"
|
||||||
|
#include "../mavlink/c_library/common/mavlink.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Serial port name
|
||||||
|
***********************************************************************/
|
||||||
|
string serialPortName;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
File descriptors
|
||||||
|
***********************************************************************/
|
||||||
|
int serial_fd;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Threading variables
|
||||||
|
***********************************************************************/
|
||||||
|
pthread_mutex_t mutex_msg;
|
||||||
|
pthread_mutex_t mutex_msgtype;
|
||||||
|
mavlink_message_t latest_msg;
|
||||||
|
sem_t sem_msg;
|
||||||
|
int msg_type = 0;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Serial tx/rx buffer size
|
||||||
|
***********************************************************************/
|
||||||
|
#define BUFSIZE 256
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int serial_setup(int baud)
|
||||||
|
|
||||||
|
Description: The serial port initialization function. This function
|
||||||
|
initializes the serial port over which DSM data is sent to
|
||||||
|
the pixhawk. A return of 0 indicates an error.
|
||||||
|
***********************************************************************/
|
||||||
|
int serial_setup(int baud)
|
||||||
|
{
|
||||||
|
struct termios options;
|
||||||
|
|
||||||
|
serial_fd = open(serialPortName.c_str(), O_RDWR | O_NOCTTY);
|
||||||
|
|
||||||
|
if(serial_fd < 0)
|
||||||
|
{
|
||||||
|
cerr << "Unable to open serial port " << serialPortName.c_str() << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcflush(serial_fd, TCIOFLUSH);
|
||||||
|
|
||||||
|
//Configure port for 8N1 transmission
|
||||||
|
tcgetattr(serial_fd, &options); //Gets the current options for the port
|
||||||
|
//Set the output baud rate
|
||||||
|
switch(baud)
|
||||||
|
{
|
||||||
|
case 57600: cfsetspeed(&options, B57600); break;
|
||||||
|
case 115200: cfsetspeed(&options, B115200); break;
|
||||||
|
case 1500000: cfsetspeed(&options, B1500000); break;
|
||||||
|
default:
|
||||||
|
cerr << "Unsupported baud rate" << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
options.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
|
||||||
|
options.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OPOST);
|
||||||
|
options.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
|
||||||
|
options.c_cflag &= ~(CSIZE | PARENB);
|
||||||
|
options.c_cflag |= CS8;
|
||||||
|
options.c_cflag |= CRTSCTS;
|
||||||
|
options.c_cc[VMIN] = 17;
|
||||||
|
options.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
tcsetattr(serial_fd, TCSANOW, &options); //Set the new options for the port "NOW"
|
||||||
|
|
||||||
|
//sleep(1);
|
||||||
|
tcflush(serial_fd, TCIOFLUSH);
|
||||||
|
|
||||||
|
cout << "Opened serial port " << serialPortName.c_str() << endl;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectUSB(void)
|
||||||
|
{
|
||||||
|
system("echo 19 >> /sys/class/gpio/export");
|
||||||
|
system("echo 21 >> /sys/class/gpio/export");
|
||||||
|
system("echo out >> /sys/class/gpio/gpio19/direction");
|
||||||
|
system("echo out >> /sys/class/gpio/gpio21/direction");
|
||||||
|
system("echo 1 >> /sys/class/gpio/gpio21/value");
|
||||||
|
system("echo 0 >> /sys/class/gpio/gpio19/value");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnectUSB(void)
|
||||||
|
{
|
||||||
|
system("echo 0 >> /sys/class/gpio/gpio21/value");
|
||||||
|
system("echo 0 >> /sys/class/gpio/gpio19/value");
|
||||||
|
system("echo 19 >> /sys/class/gpio/unexport");
|
||||||
|
system("echo 21 >> /sys/class/gpio/unexport");
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int mavlink_task()
|
||||||
|
|
||||||
|
Description: Thread task to pull mavlink data from the serial port
|
||||||
|
and dump it into a mutex-protected mavlink message.
|
||||||
|
|
||||||
|
When log data begins streaming, dumps log data into the
|
||||||
|
log file on the filesystem.
|
||||||
|
***********************************************************************/
|
||||||
|
void *mavlink_task(void*)
|
||||||
|
{
|
||||||
|
mavlink_message_t _msg;
|
||||||
|
mavlink_status_t mavlink_status;
|
||||||
|
int read_len;
|
||||||
|
int i;
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
int msgtolookfor;
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
read_len = read(serial_fd, buf, BUFSIZE);
|
||||||
|
|
||||||
|
if (read_len < 0)
|
||||||
|
{
|
||||||
|
if (errno != EAGAIN)
|
||||||
|
cerr << "Read err: " << errno << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(read_len > 0)
|
||||||
|
{
|
||||||
|
for(i=0; i<read_len; ++i)
|
||||||
|
{
|
||||||
|
if(mavlink_parse_char(MAVLINK_COMM_0, buf[i], &_msg, &mavlink_status))
|
||||||
|
{
|
||||||
|
//If this is the type of message we're waiting for, put it on
|
||||||
|
//the latest_msg or handle its log data.
|
||||||
|
pthread_mutex_lock(&mutex_msgtype);
|
||||||
|
msgtolookfor = msg_type;
|
||||||
|
pthread_mutex_unlock(&mutex_msgtype);
|
||||||
|
|
||||||
|
if(_msg.msgid == msgtolookfor)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mutex_msg);
|
||||||
|
memcpy(&latest_msg, &_msg, sizeof(mavlink_message_t));
|
||||||
|
pthread_mutex_unlock(&mutex_msg);
|
||||||
|
sem_post(&sem_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int wait_for_message()
|
||||||
|
|
||||||
|
Description: Blocks until a particular mavlink message type is received.
|
||||||
|
A return value of 0 indicates succes, -1 for a timeout.
|
||||||
|
Setting timeout_s to 0 blocks indefinitely.
|
||||||
|
***********************************************************************/
|
||||||
|
int wait_for_message(int _msg_type, mavlink_message_t *msg, int timeout_s)
|
||||||
|
{
|
||||||
|
struct timespec timeout;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
//Tell the mavlink thread which message to look for
|
||||||
|
pthread_mutex_lock(&mutex_msgtype);
|
||||||
|
msg_type = _msg_type;
|
||||||
|
pthread_mutex_unlock(&mutex_msgtype);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_REALTIME, &timeout);
|
||||||
|
timeout.tv_sec += timeout_s;
|
||||||
|
|
||||||
|
if(timeout_s == 0)
|
||||||
|
sem_wait(&sem_msg);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = sem_timedwait(&sem_msg, &timeout);
|
||||||
|
|
||||||
|
if(ret < 0)
|
||||||
|
{
|
||||||
|
if (errno == ETIMEDOUT)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&mutex_msg);
|
||||||
|
memcpy(msg, &latest_msg, sizeof(mavlink_message_t));
|
||||||
|
pthread_mutex_unlock(&mutex_msg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int open_USB_serial(void)
|
||||||
|
{
|
||||||
|
char usbdev_string[256] = "usb-3D_Robotics_PX4_FMU";
|
||||||
|
struct dirent *direntry;
|
||||||
|
bool foundDevice = false;
|
||||||
|
DIR *dir;
|
||||||
|
|
||||||
|
connectUSB();
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
dir = opendir("/dev/serial/by-id/");
|
||||||
|
if (dir == NULL) {
|
||||||
|
cerr << "open /dev/serial/by-id failed" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while((direntry = readdir(dir)))
|
||||||
|
{
|
||||||
|
if(!strncmp(direntry->d_name, usbdev_string, 23))
|
||||||
|
{
|
||||||
|
foundDevice = true;
|
||||||
|
strcpy(usbdev_string,direntry->d_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!foundDevice)
|
||||||
|
{
|
||||||
|
cerr << "Unable to find USB device" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
//Serial port setup
|
||||||
|
serialPortName = "/dev/serial/by-id/";
|
||||||
|
serialPortName.append(usbdev_string);
|
||||||
|
if(!serial_setup(115200))
|
||||||
|
{
|
||||||
|
cerr << "Unable to initialize the USB serial port" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: void request_baudrate()
|
||||||
|
|
||||||
|
Description: Request a particular log file from the pixhawk.
|
||||||
|
***********************************************************************/
|
||||||
|
void request_baudrate(void)
|
||||||
|
{
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
mavlink_message_t msg;
|
||||||
|
int msg_len;
|
||||||
|
float baud;
|
||||||
|
int tries;
|
||||||
|
bool gotParam=false;
|
||||||
|
|
||||||
|
mavlink_msg_param_request_read_pack(1, 1, &msg, 1, 1, "SERIAL1_BAUD", -1);
|
||||||
|
msg_len = mavlink_msg_to_send_buffer((uint8_t*)buf,&msg);
|
||||||
|
|
||||||
|
if(write(serial_fd, buf, msg_len) != msg_len)
|
||||||
|
{
|
||||||
|
cerr << "Serial port write error." << endl;
|
||||||
|
disconnectUSB();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(tries=0; tries<3; ++tries)
|
||||||
|
{
|
||||||
|
cout << "Requesting SERIAL1_BAUD..." << endl;
|
||||||
|
|
||||||
|
if(wait_for_message(MAVLINK_MSG_ID_PARAM_VALUE, &msg, 3) >= 0)
|
||||||
|
{
|
||||||
|
baud = mavlink_msg_param_value_get_param_value(&msg);
|
||||||
|
cout << "Got param value " << baud << endl;
|
||||||
|
disconnectUSB();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!gotParam)
|
||||||
|
{
|
||||||
|
cout << "Unable to get SERIAL1_BAUD_VALUE" << endl;
|
||||||
|
disconnectUSB();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: int main(void)
|
||||||
|
|
||||||
|
Description: The main function. Initializes and runs the serial and
|
||||||
|
UDP threads.
|
||||||
|
***********************************************************************/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int baudrate;
|
||||||
|
pthread_t mavlink_ctx;
|
||||||
|
mavlink_message_t msg;
|
||||||
|
|
||||||
|
//Cout cleanup
|
||||||
|
std::cout.precision(2);
|
||||||
|
std::cout.setf( ios::fixed, ios::floatfield );
|
||||||
|
|
||||||
|
cout << "Pixhawk baudrate checker" << endl;
|
||||||
|
|
||||||
|
//Serial port setup
|
||||||
|
serialPortName = "/dev/ttymxc1";
|
||||||
|
baudrate=115200;//57600;
|
||||||
|
if(!serial_setup(baudrate))
|
||||||
|
{
|
||||||
|
cerr << "Unable to initialize the serial send" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Threading initialization
|
||||||
|
pthread_mutex_init(&mutex_msg, NULL);
|
||||||
|
sem_init(&sem_msg, 0, 0);
|
||||||
|
|
||||||
|
//Start the mavlink rx thread
|
||||||
|
pthread_create(&mavlink_ctx, NULL, mavlink_task, NULL);
|
||||||
|
|
||||||
|
cout << "Waiting for a hearbeat..." << endl;
|
||||||
|
|
||||||
|
//Wait for a heartbeat on the telem port
|
||||||
|
if(wait_for_message(MAVLINK_MSG_ID_HEARTBEAT, &msg, 5) < 0)
|
||||||
|
{
|
||||||
|
cout << "No heartbeat received, requesting baudrate from USB" << endl;
|
||||||
|
pthread_cancel(mavlink_ctx);
|
||||||
|
close(serial_fd);
|
||||||
|
if (open_USB_serial() < 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "Got a heartbeat, exiting" << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_create(&mavlink_ctx, NULL, mavlink_task, NULL);
|
||||||
|
|
||||||
|
//Send a param request to the pixhawk
|
||||||
|
request_baudrate();
|
||||||
|
|
||||||
|
//Wait for the mavlink thread to end
|
||||||
|
pthread_join(mavlink_ctx, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
370
sololink/flightcode/command/Commander.cpp
Normal file
370
sololink/flightcode/command/Commander.cpp
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include "util.h"
|
||||||
|
#include "mutex.h"
|
||||||
|
#include "Commander.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
// Constructor creates and opens the command socket, then starts the command
|
||||||
|
// thread.
|
||||||
|
Commander::Commander(const char *sock_name)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (strlen(sock_name) >= sizeof(sa.sun_path)) {
|
||||||
|
cerr << "ERROR: command socket name too long" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is something already there with the socket name, it is deleted.
|
||||||
|
// If not, unlink() fails (and we don't care).
|
||||||
|
(void)unlink(sock_name);
|
||||||
|
|
||||||
|
// create command socket
|
||||||
|
_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
|
if (_fd < 0) {
|
||||||
|
cerr << "ERROR creating command socket" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind command socket to address
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sun_family = AF_UNIX;
|
||||||
|
strncpy(sa.sun_path, sock_name, sizeof(sa.sun_path) - 1);
|
||||||
|
if (bind(_fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) {
|
||||||
|
cerr << "ERROR binding command socket" << endl;
|
||||||
|
close(_fd);
|
||||||
|
_fd = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize clients list mutex
|
||||||
|
if (mutex_init(&clients_mutex) != 0) {
|
||||||
|
cerr << "ERROR initializing clients mutex" << endl;
|
||||||
|
close(_fd);
|
||||||
|
_fd = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start command thread
|
||||||
|
if (pthread_create(&thread_id, NULL, &Commander::command_entry, this) != 0) {
|
||||||
|
cerr << "ERROR starting command thread" << endl;
|
||||||
|
close(_fd);
|
||||||
|
_fd = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Commander::Commander
|
||||||
|
|
||||||
|
|
||||||
|
// Destructor sends a QUIT command to the command thread, waits for it to exit,
|
||||||
|
// then closes the command socket.
|
||||||
|
Commander::~Commander()
|
||||||
|
{
|
||||||
|
const char *quit = "QUIT";
|
||||||
|
ssize_t quit_len = strlen(quit);
|
||||||
|
|
||||||
|
if (sendto(_fd, quit, quit_len, 0,
|
||||||
|
(const struct sockaddr *)&sa, sizeof(sa)) != quit_len) {
|
||||||
|
cerr << "~Commander: ERROR returned from sendto" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_join(thread_id, NULL) != 0) {
|
||||||
|
cerr << "ERROR returned from pthread_join" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(_fd);
|
||||||
|
|
||||||
|
} // Commander::~Commander()
|
||||||
|
|
||||||
|
|
||||||
|
// Commander processing thread.
|
||||||
|
// Wait for a command on the command socket, and process each. A QUIT command
|
||||||
|
// causes the loop to exit.
|
||||||
|
void *Commander::command_thread(void)
|
||||||
|
{
|
||||||
|
bool quit = false;
|
||||||
|
char buf[MAX_CMD_LEN];
|
||||||
|
int nb;
|
||||||
|
struct sockaddr_storage src_storage;
|
||||||
|
struct sockaddr *src = (struct sockaddr *)&src_storage;
|
||||||
|
socklen_t src_len;
|
||||||
|
char *token;
|
||||||
|
const char *delims;
|
||||||
|
|
||||||
|
//cout << "command_thread: running" << endl;
|
||||||
|
|
||||||
|
while (!quit) {
|
||||||
|
|
||||||
|
src_len = sizeof(struct sockaddr_storage);
|
||||||
|
memset(src, 0, src_len);
|
||||||
|
memset(&buf, 0, sizeof(buf));
|
||||||
|
nb = recvfrom(_fd, buf, MAX_CMD_LEN, 0, src, &src_len);
|
||||||
|
if (nb < 0) {
|
||||||
|
cerr << "ERROR returned from recvfrom" << endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
delims = " \t\r\n";
|
||||||
|
token = strtok(buf, delims);
|
||||||
|
|
||||||
|
if (token == NULL) {
|
||||||
|
// no command, like ping
|
||||||
|
handle_ping(src, src_len);
|
||||||
|
} else if (strcasecmp(token, "ATTACH") == 0) {
|
||||||
|
token = strtok(NULL, delims);
|
||||||
|
handle_attach(src, src_len, token, true);
|
||||||
|
} else if (strcasecmp(token, "DETACH") == 0) {
|
||||||
|
token = strtok(NULL, delims);
|
||||||
|
handle_attach(src, src_len, token, false);
|
||||||
|
} else if (strcasecmp(token, "PING") == 0) {
|
||||||
|
handle_ping(src, src_len);
|
||||||
|
} else if (strcasecmp(token, "LIST") == 0) {
|
||||||
|
handle_list(src, src_len);
|
||||||
|
} else if (strcasecmp(token, "QUIT") == 0) {
|
||||||
|
quit = true;
|
||||||
|
} else {
|
||||||
|
cerr << "Unknown command: " << buf << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // while (!quit)
|
||||||
|
|
||||||
|
//cout << "command_thread: exiting" << endl;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} // Commander::command_thread
|
||||||
|
|
||||||
|
|
||||||
|
// Send data to all clients.
|
||||||
|
// The supplied data is sent to all clients in the client list, i.e. all
|
||||||
|
// clients for which an ATTACH command has been sent. Unix domain datagram
|
||||||
|
// sockets are reliable, which means a write will block if the reader is
|
||||||
|
// not fast enough. We can't let an RC packet consumer block the UDP thread,
|
||||||
|
// so the sendto is done nonblocking. This means it is up to the reader to
|
||||||
|
// read fast enough to keep from dropping packets.
|
||||||
|
void Commander::send_clients(const void *data, int data_len)
|
||||||
|
{
|
||||||
|
static unsigned drops = 0;
|
||||||
|
vector<SockAddr>::iterator it;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&clients_mutex);
|
||||||
|
|
||||||
|
for (it = clients.begin(); it != clients.end(); it++) {
|
||||||
|
if (sendto(_fd, data, data_len, MSG_DONTWAIT,
|
||||||
|
it->sockaddr(), it->socklen()) != data_len) {
|
||||||
|
//cerr << "send_clients: \"" << strerror(errno)
|
||||||
|
// << "\" sending to " << *it << endl;
|
||||||
|
drops++;
|
||||||
|
} else {
|
||||||
|
//cout << "send_clients: sent to " << *it << endl;
|
||||||
|
if (drops > 0) {
|
||||||
|
syslog(LOG_INFO, "cmd: dropped %d packets", drops);
|
||||||
|
drops = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&clients_mutex);
|
||||||
|
|
||||||
|
} // Commander::send_clients
|
||||||
|
|
||||||
|
|
||||||
|
// Handle ATTACH or DETACH command.
|
||||||
|
//
|
||||||
|
// ATTACH/DETACH takes and optional argument indicating the client to ATTACH or
|
||||||
|
// detach. If no argument is given, the sender of the command is used. If an
|
||||||
|
// argument is given, it should be an AF_UNIX socket (support for UDP sockets
|
||||||
|
// could be added).
|
||||||
|
//
|
||||||
|
// If the command was ATTACH and the client is not in the list of clients, add
|
||||||
|
// it to the list. If the command was DETACH and the client is in the list of
|
||||||
|
// clients, remove it from the list.
|
||||||
|
void Commander::handle_attach(const struct sockaddr *src_addr,
|
||||||
|
socklen_t src_addr_len, const char *client,
|
||||||
|
bool attach)
|
||||||
|
{
|
||||||
|
struct sockaddr_storage client_addr;
|
||||||
|
socklen_t client_addr_len;
|
||||||
|
|
||||||
|
memset(&client_addr, 0, sizeof(client_addr));
|
||||||
|
client_addr_len = 0;
|
||||||
|
|
||||||
|
if (client == NULL) {
|
||||||
|
// no client given - use source of request as client
|
||||||
|
memcpy(&client_addr, src_addr, src_addr_len);
|
||||||
|
client_addr_len = src_addr_len;
|
||||||
|
} else {
|
||||||
|
// client should be a unix socket name
|
||||||
|
// (support for IP:port could be added)
|
||||||
|
struct stat stat_buf;
|
||||||
|
if (stat(client, &stat_buf) != 0) {
|
||||||
|
cerr << "handle_attach: ERROR: \"" << client
|
||||||
|
<< "\" does not exist" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!S_ISSOCK(stat_buf.st_mode)) {
|
||||||
|
cerr << "handle_attach: ERROR: \"" << client
|
||||||
|
<< "\" exists but is not a socket" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct sockaddr_un *c_un = (struct sockaddr_un *)&client_addr;
|
||||||
|
int maxlen = sizeof(c_un->sun_path);
|
||||||
|
c_un->sun_family = AF_UNIX;
|
||||||
|
strncpy(c_un->sun_path, client, maxlen - 1);
|
||||||
|
c_un->sun_path[maxlen - 1] = '\0';
|
||||||
|
client_addr_len = offsetof(struct sockaddr_un, sun_path)
|
||||||
|
+ strlen(c_un->sun_path) + 1; // man 7 unix
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr s((struct sockaddr *)&client_addr, client_addr_len);
|
||||||
|
bool found = false;
|
||||||
|
vector<SockAddr>::iterator it;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&clients_mutex);
|
||||||
|
|
||||||
|
for (it = clients.begin(); it != clients.end(); it++) {
|
||||||
|
if (*it == s) { // == overloaded
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attach && !found) {
|
||||||
|
// attach new client
|
||||||
|
clients.push_back(s);
|
||||||
|
} else if (!attach && found) {
|
||||||
|
// detach existing client
|
||||||
|
clients.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&clients_mutex);
|
||||||
|
|
||||||
|
send_response(src_addr, src_addr_len, "OK\n");
|
||||||
|
|
||||||
|
} // Commander::handle_attach
|
||||||
|
|
||||||
|
|
||||||
|
// Handle LIST command.
|
||||||
|
// Response to LIST is attached clients, one per line, then "OK\n".
|
||||||
|
void Commander::handle_list(const struct sockaddr *src_addr,
|
||||||
|
socklen_t src_addr_len)
|
||||||
|
{
|
||||||
|
string response;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&clients_mutex);
|
||||||
|
|
||||||
|
vector<SockAddr>::iterator it;
|
||||||
|
for (it = clients.begin(); it != clients.end(); it++)
|
||||||
|
response = response + it->to_string() + "\n";
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&clients_mutex);
|
||||||
|
|
||||||
|
response += "OK\n";
|
||||||
|
|
||||||
|
send_response(src_addr, src_addr_len, response.c_str());
|
||||||
|
|
||||||
|
} // Commander::handle_list
|
||||||
|
|
||||||
|
|
||||||
|
// Handle PING command.
|
||||||
|
// Response to PING is simply "OK\n".
|
||||||
|
void Commander::handle_ping(const struct sockaddr *src_addr,
|
||||||
|
socklen_t src_addr_len)
|
||||||
|
{
|
||||||
|
send_response(src_addr, src_addr_len, "OK\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send a reponse to a client command.
|
||||||
|
void Commander::send_response(const struct sockaddr *src_addr,
|
||||||
|
socklen_t src_addr_len,
|
||||||
|
const char *response, ssize_t response_len)
|
||||||
|
{
|
||||||
|
if (response_len == -1)
|
||||||
|
response_len = strlen(response);
|
||||||
|
|
||||||
|
if (sendto(_fd, response, response_len, 0, src_addr, src_addr_len)
|
||||||
|
!= response_len) {
|
||||||
|
cerr << "send_response: ERROR returned from sendto" << endl;
|
||||||
|
cerr << "send_response: \"" << strerror(errno)
|
||||||
|
<< "\" sending to " << *src_addr << endl;
|
||||||
|
} else {
|
||||||
|
//cout << "send_response: OK" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Commander::send_response
|
||||||
|
|
||||||
|
|
||||||
|
// print SockAddr to stream (human-readable)
|
||||||
|
ostream& operator<<(ostream& os, const SockAddr& sock)
|
||||||
|
{
|
||||||
|
sa_family_t family = sock.su.sa.sa_family;
|
||||||
|
|
||||||
|
if (family == AF_UNIX) {
|
||||||
|
const struct sockaddr_un *un = &sock.su.un;
|
||||||
|
os << "AF_UNIX: " << un->sun_path;
|
||||||
|
} else if (family == AF_INET) {
|
||||||
|
const struct sockaddr_in *in = &sock.su.in;
|
||||||
|
os << "AF_INET: " << inet_ntoa(in->sin_addr) << ":"
|
||||||
|
<< ntohs(in->sin_port);
|
||||||
|
} else {
|
||||||
|
os << "sa_family=" << family;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// print sockaddr/sockaddr_un/sockaddr_in to stream (human-readable)
|
||||||
|
ostream& operator<<(ostream& os, const struct sockaddr& sa)
|
||||||
|
{
|
||||||
|
sa_family_t family = sa.sa_family;
|
||||||
|
|
||||||
|
if (family == AF_UNIX) {
|
||||||
|
const struct sockaddr_un *un = (const struct sockaddr_un *)&sa;
|
||||||
|
os << "AF_UNIX: " << un->sun_path;
|
||||||
|
} else if (family == AF_INET) {
|
||||||
|
const struct sockaddr_in *in = (const struct sockaddr_in *)&sa;
|
||||||
|
os << "AF_INET: " << inet_ntoa(in->sin_addr) << ":"
|
||||||
|
<< ntohs(in->sin_port);
|
||||||
|
} else {
|
||||||
|
os << "sa_family=" << family;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
Commander *ss;
|
||||||
|
|
||||||
|
cout << "main: creating command object" << endl;
|
||||||
|
ss = new Commander(argv[1]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
ss->send_clients("hello\n", 6);
|
||||||
|
sleep(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "main: deleting command object" << endl;
|
||||||
|
delete ss;
|
||||||
|
|
||||||
|
cout << "main: exiting" << endl;
|
||||||
|
|
||||||
|
} // main
|
||||||
|
|
||||||
|
#endif
|
128
sololink/flightcode/command/Commander.h
Normal file
128
sololink/flightcode/command/Commander.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, const struct sockaddr& sa);
|
||||||
|
|
||||||
|
|
||||||
|
// Simple wrapper for sockaddr and its variants
|
||||||
|
class SockAddr
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Constructor mainly just checks that the supplied sockaddr fits
|
||||||
|
// (else is is probably corrupt).
|
||||||
|
SockAddr(struct sockaddr *s, socklen_t len)
|
||||||
|
{
|
||||||
|
memset(&su, 0, sizeof(su));
|
||||||
|
sl = len;
|
||||||
|
if (sl > sizeof(su))
|
||||||
|
sl = sizeof(su);
|
||||||
|
memcpy(&su, s, sl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructor doesn't need to do anything; memory cleared for fun.
|
||||||
|
~SockAddr()
|
||||||
|
{
|
||||||
|
memset(&su, 0, sizeof(su));
|
||||||
|
sl = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used when adding/deleting a new SockAddr to see if a particular
|
||||||
|
// one is already in the list
|
||||||
|
const bool operator==(const SockAddr& rhs)
|
||||||
|
{
|
||||||
|
// address does not have to match after 'sl' bytes
|
||||||
|
return (sl == rhs.sl) && (memcmp(&su, &rhs.su, sl) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'string' representation is used in the response to a 'list'
|
||||||
|
// command, where a list of all SockAddrs is being sent back. Uses
|
||||||
|
// operator<< for the conversion.
|
||||||
|
string to_string(void)
|
||||||
|
{
|
||||||
|
ostringstream ss;
|
||||||
|
ss << *this;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// member accessor
|
||||||
|
const struct sockaddr *sockaddr()
|
||||||
|
{
|
||||||
|
return &su.sa;
|
||||||
|
}
|
||||||
|
|
||||||
|
// member accessor
|
||||||
|
const socklen_t socklen()
|
||||||
|
{
|
||||||
|
return sl;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend ostream& operator<<(ostream&, const SockAddr&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// The actual socket address. In most cases an anonymous union would
|
||||||
|
// be better, but there are some cases where we want the maximum size.
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_in in;
|
||||||
|
struct sockaddr_un un;
|
||||||
|
} su;
|
||||||
|
|
||||||
|
// sockaddr_un requires the length to fully specify the address
|
||||||
|
// (see 'man 7 unix', pathname vs. unnamed vs. abstract). We only
|
||||||
|
// support the pathname variant a the moment, but this is maintained
|
||||||
|
// for possible future enhancement.
|
||||||
|
socklen_t sl;
|
||||||
|
|
||||||
|
}; // SockAddr
|
||||||
|
|
||||||
|
|
||||||
|
class Commander
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Commander(const char *sock_name);
|
||||||
|
~Commander();
|
||||||
|
void send_clients(const void *data, int num_bytes);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int _fd;
|
||||||
|
virtual void handle_attach(const struct sockaddr *, socklen_t,
|
||||||
|
const char *, bool);
|
||||||
|
virtual void handle_list(const struct sockaddr *, socklen_t);
|
||||||
|
virtual void handle_ping(const struct sockaddr *, socklen_t);
|
||||||
|
void send_response(const struct sockaddr *, socklen_t,
|
||||||
|
const char *, ssize_t response_len=-1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct sockaddr_un sa;
|
||||||
|
pthread_t thread_id;
|
||||||
|
vector<SockAddr> clients;
|
||||||
|
pthread_mutex_t clients_mutex;
|
||||||
|
|
||||||
|
void *command_thread(void);
|
||||||
|
|
||||||
|
static const int MAX_CMD_LEN = 64;
|
||||||
|
|
||||||
|
// the purpose of this is to give pthread_create a class static
|
||||||
|
// function that calls back to an object method
|
||||||
|
static void *command_entry(void *arg)
|
||||||
|
{
|
||||||
|
Commander *me = (Commander *)arg;
|
||||||
|
return me->command_thread();
|
||||||
|
}
|
||||||
|
};
|
38
sololink/flightcode/command/Makefile
Normal file
38
sololink/flightcode/command/Makefile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
CFLAGS += -Wall -g
|
||||||
|
CXXFLAGS += -Wall -g
|
||||||
|
|
||||||
|
LIBS = -lpthread
|
||||||
|
|
||||||
|
SRCS_CPP = Commander.cpp
|
||||||
|
#SRCS_CPP += ../ini/cpp/INIReader.cpp ../log/Log.cpp
|
||||||
|
#SRCS_C = ../ini/ini.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_CPP:.cpp=.o) $(SRCS_C:.c=.o)
|
||||||
|
|
||||||
|
MAIN = Commander
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
Commander.o: Commander.cpp Commander.h
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.cpp) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
#$(RM) ../ini/*.o
|
||||||
|
#$(RM) ../ini/cpp/*.o
|
||||||
|
#$(RM) ../log/*.o
|
||||||
|
|
||||||
|
.PHONY: clean
|
56
sololink/flightcode/dataflash_logger/Makefile
Normal file
56
sololink/flightcode/dataflash_logger/Makefile
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
VPATH = ../util ../ini ../ini/cpp
|
||||||
|
|
||||||
|
INCS = -I../util -I../ini -I../ini/cpp
|
||||||
|
|
||||||
|
CFLAGS += -Wall $(INCS)
|
||||||
|
CXXFLAGS += -Wall $(INCS) -std=c++11
|
||||||
|
|
||||||
|
SRCS_CPP = dataflash_logger.cpp
|
||||||
|
SRCS_CPP += common_tool.cpp
|
||||||
|
SRCS_CPP += format_reader.cpp
|
||||||
|
SRCS_CPP += telem_forwarder_client.cpp
|
||||||
|
SRCS_CPP += mavlink_reader.cpp
|
||||||
|
SRCS_CPP += analyzer_util.cpp
|
||||||
|
SRCS_CPP += dataflash_logger_program.cpp
|
||||||
|
SRCS_CPP += INIReader.cpp
|
||||||
|
SRCS_CPP += mavlink_message_handler.cpp
|
||||||
|
SRCS_CPP += mavlink_writer.cpp
|
||||||
|
SRCS_CPP += telem_client.cpp
|
||||||
|
SRCS_CPP += telem_serial.cpp
|
||||||
|
SRCS_CPP += la-log.cpp
|
||||||
|
SRCS_CPP += heart.cpp
|
||||||
|
SRCS_C = util.c ini.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_CPP:.cpp=.o) $(SRCS_C:.c=.o)
|
||||||
|
LIBS=-pthread
|
||||||
|
|
||||||
|
MAIN = dataflash_logger # actually, the main is in mavlink_reader...
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.cpp) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: clean
|
264
sololink/flightcode/dataflash_logger/MsgHandler.cpp
Normal file
264
sololink/flightcode/dataflash_logger/MsgHandler.cpp
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
#include "MsgHandler.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void fatal(const char *msg)
|
||||||
|
{
|
||||||
|
::printf("%s", msg);
|
||||||
|
::printf("\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *xstrdup(const char *string)
|
||||||
|
{
|
||||||
|
char *ret = strdup(string);
|
||||||
|
if (ret == NULL) {
|
||||||
|
perror("strdup");
|
||||||
|
fatal("strdup failed");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::add_field_type(char type, size_t size)
|
||||||
|
{
|
||||||
|
size_for_type_table[(type > 'A' ? (type - 'A') : (type - 'a'))] = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MsgHandler::size_for_type(char type)
|
||||||
|
{
|
||||||
|
return size_for_type_table[(uint8_t)(type > 'A' ? (type - 'A') : (type - 'a'))];
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::init_field_types()
|
||||||
|
{
|
||||||
|
add_field_type('b', sizeof(int8_t));
|
||||||
|
add_field_type('c', sizeof(int16_t));
|
||||||
|
add_field_type('e', sizeof(int32_t));
|
||||||
|
add_field_type('f', sizeof(float));
|
||||||
|
add_field_type('h', sizeof(int16_t));
|
||||||
|
add_field_type('i', sizeof(int32_t));
|
||||||
|
add_field_type('n', sizeof(char[4]));
|
||||||
|
add_field_type('B', sizeof(uint8_t));
|
||||||
|
add_field_type('C', sizeof(uint16_t));
|
||||||
|
add_field_type('E', sizeof(uint32_t));
|
||||||
|
add_field_type('H', sizeof(uint16_t));
|
||||||
|
add_field_type('I', sizeof(uint32_t));
|
||||||
|
add_field_type('L', sizeof(int32_t));
|
||||||
|
add_field_type('M', sizeof(uint8_t));
|
||||||
|
add_field_type('N', sizeof(char[16]));
|
||||||
|
add_field_type('Z', sizeof(char[64]));
|
||||||
|
add_field_type('q', sizeof(int64_t));
|
||||||
|
add_field_type('Q', sizeof(uint64_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MsgHandler::format_field_info *MsgHandler::find_field_info(const char *label)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < next_field; i++) {
|
||||||
|
if (streq(field_info[i].label, label)) {
|
||||||
|
return &field_info[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgHandler::MsgHandler(const struct log_Format &_f) : next_field(0), f(_f)
|
||||||
|
{
|
||||||
|
init_field_types();
|
||||||
|
parse_format_fields();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::add_field(const char *_label, uint8_t _type, uint8_t _offset, uint8_t _length)
|
||||||
|
{
|
||||||
|
field_info[next_field].label = xstrdup(_label);
|
||||||
|
field_info[next_field].type = _type;
|
||||||
|
field_info[next_field].offset = _offset;
|
||||||
|
field_info[next_field].length = _length;
|
||||||
|
next_field++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::parse_format_fields()
|
||||||
|
{
|
||||||
|
char *labels = xstrdup(f.labels);
|
||||||
|
char *arg = labels;
|
||||||
|
uint8_t label_offset = 0;
|
||||||
|
char *next_label;
|
||||||
|
uint8_t msg_offset = 3; // 3 bytes for the header
|
||||||
|
|
||||||
|
while ((next_label = strtok(arg, ",")) != NULL) {
|
||||||
|
if (label_offset > strlen(f.format)) {
|
||||||
|
free(labels);
|
||||||
|
printf("too few field times for labels %s (format=%s) (labels=%s)\n", f.name, f.format,
|
||||||
|
f.labels);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
uint8_t field_type = f.format[label_offset];
|
||||||
|
uint8_t length = size_for_type(field_type);
|
||||||
|
add_field(next_label, field_type, msg_offset, length);
|
||||||
|
arg = NULL;
|
||||||
|
msg_offset += length;
|
||||||
|
label_offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label_offset != strlen(f.format)) {
|
||||||
|
printf("too few labels for format (format=%s) (labels=%s)\n", f.format, f.labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MsgHandler::field_value(const uint8_t *msg, const char *label, char *ret, uint8_t retlen)
|
||||||
|
{
|
||||||
|
struct format_field_info *info = find_field_info(label);
|
||||||
|
if (info == NULL) {
|
||||||
|
::printf("No info for (%s)\n", label);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t offset = info->offset;
|
||||||
|
if (offset == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(ret, '\0', retlen);
|
||||||
|
|
||||||
|
memcpy(ret, &msg[offset], (retlen < info->length) ? retlen : info->length);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MsgHandler::field_value(uint8_t *msg, const char *label, Vector3f &ret)
|
||||||
|
{
|
||||||
|
const char *axes = "XYZ";
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < next_field; i++) {
|
||||||
|
if (!strncmp(field_info[i].label, label, strlen(label)) &&
|
||||||
|
strlen(field_info[i].label) == strlen(label) + 1) {
|
||||||
|
for (uint8_t j = 0; j < 3; j++) {
|
||||||
|
if (field_info[i].label[strlen(label)] == axes[j]) {
|
||||||
|
field_value_for_type_at_offset(msg, field_info[i].type, field_info[i].offset,
|
||||||
|
ret[j]);
|
||||||
|
break; // break from finding-label loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == next_field) {
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::string_for_labels(char *buffer, uint bufferlen)
|
||||||
|
{
|
||||||
|
memset(buffer, '\0', bufferlen);
|
||||||
|
bufferlen--;
|
||||||
|
|
||||||
|
char *pos = buffer;
|
||||||
|
for (uint8_t k = 0; k < LOGREADER_MAX_FIELDS; k++) {
|
||||||
|
if (field_info[k].label != NULL) {
|
||||||
|
uint8_t remaining = bufferlen - (pos - buffer);
|
||||||
|
uint8_t label_length = strlen(field_info[k].label);
|
||||||
|
uint8_t required = label_length;
|
||||||
|
if (pos != buffer) { // room for a comma
|
||||||
|
required++;
|
||||||
|
}
|
||||||
|
if (required + 1 > remaining) { // null termination
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos != buffer) {
|
||||||
|
*pos++ = ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(pos, field_info[k].label, label_length);
|
||||||
|
pos += label_length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgHandler::~MsgHandler()
|
||||||
|
{
|
||||||
|
for (uint8_t k = 0; k < LOGREADER_MAX_FIELDS; k++) {
|
||||||
|
if (field_info[k].label != NULL) {
|
||||||
|
free(field_info[k].label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::location_from_msg(uint8_t *msg, Location &loc, const char *label_lat,
|
||||||
|
const char *label_long, const char *label_alt)
|
||||||
|
{
|
||||||
|
loc.lat = require_field_int32_t(msg, label_lat);
|
||||||
|
loc.lng = require_field_int32_t(msg, label_long);
|
||||||
|
loc.alt = require_field_int32_t(msg, label_alt);
|
||||||
|
loc.options = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::ground_vel_from_msg(uint8_t *msg, Vector3f &vel, const char *label_speed,
|
||||||
|
const char *label_course, const char *label_vz)
|
||||||
|
{
|
||||||
|
uint32_t ground_speed;
|
||||||
|
int32_t ground_course;
|
||||||
|
require_field(msg, label_speed, ground_speed);
|
||||||
|
require_field(msg, label_course, ground_course);
|
||||||
|
vel[0] = ground_speed * 0.01f * cosf(radians(ground_course * 0.01f));
|
||||||
|
vel[1] = ground_speed * 0.01f * sinf(radians(ground_course * 0.01f));
|
||||||
|
vel[2] = require_field_float(msg, label_vz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::attitude_from_msg(uint8_t *msg, Vector3f &att, const char *label_roll,
|
||||||
|
const char *label_pitch, const char *label_yaw)
|
||||||
|
{
|
||||||
|
att[0] = require_field_int16_t(msg, label_roll) * 0.01f;
|
||||||
|
att[1] = require_field_int16_t(msg, label_pitch) * 0.01f;
|
||||||
|
att[2] = require_field_uint16_t(msg, label_yaw) * 0.01f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::field_not_found(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
char all_labels[256];
|
||||||
|
uint8_t type = msg[2];
|
||||||
|
string_for_labels(all_labels, 256);
|
||||||
|
::printf("Field (%s) not found for id=%d; options are (%s)\n", label, type, all_labels);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MsgHandler::require_field(const uint8_t *msg, const char *label, char *buffer,
|
||||||
|
uint8_t bufferlen)
|
||||||
|
{
|
||||||
|
if (!field_value(msg, label, buffer, bufferlen)) {
|
||||||
|
field_not_found(msg, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float MsgHandler::require_field_float(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
float ret;
|
||||||
|
require_field(msg, label, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
uint8_t MsgHandler::require_field_uint8_t(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
uint8_t ret;
|
||||||
|
require_field(msg, label, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
int32_t MsgHandler::require_field_int32_t(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
int32_t ret;
|
||||||
|
require_field(msg, label, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
uint16_t MsgHandler::require_field_uint16_t(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
uint16_t ret;
|
||||||
|
require_field(msg, label, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
int16_t MsgHandler::require_field_int16_t(const uint8_t *msg, const char *label)
|
||||||
|
{
|
||||||
|
int16_t ret;
|
||||||
|
require_field(msg, label, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
152
sololink/flightcode/dataflash_logger/MsgHandler.h
Normal file
152
sololink/flightcode/dataflash_logger/MsgHandler.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
#ifndef AP_MSGHANDLER_H
|
||||||
|
#define AP_MSGHANDLER_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h> // for abort()
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#define radians(x) (x / 180 * M_PI)
|
||||||
|
#include "Vector3f.h"
|
||||||
|
#include "Location.h"
|
||||||
|
#include "DataFlash/LogMessage.h"
|
||||||
|
|
||||||
|
#define LOGREADER_MAX_FIELDS 30
|
||||||
|
|
||||||
|
#ifndef streq
|
||||||
|
#define streq(x, y) (!strcmp(x, y))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class MsgHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// constructor - create a parser for a MavLink message format
|
||||||
|
MsgHandler(const struct log_Format &f);
|
||||||
|
|
||||||
|
// retrieve a comma-separated list of all labels
|
||||||
|
void string_for_labels(char *buffer, uint bufferlen);
|
||||||
|
|
||||||
|
// field_value - retrieve the value of a field from the supplied message
|
||||||
|
// these return false if the field was not found
|
||||||
|
template < typename R >
|
||||||
|
bool field_value(const uint8_t *msg, const char *label, R &ret);
|
||||||
|
|
||||||
|
bool field_value(uint8_t *msg, const char *label, Vector3f &ret);
|
||||||
|
bool field_value(const uint8_t *msg, const char *label, char *buffer, uint8_t bufferlen);
|
||||||
|
|
||||||
|
template < typename R >
|
||||||
|
void require_field(const uint8_t *msg, const char *label, R &ret)
|
||||||
|
{
|
||||||
|
if (!field_value(msg, label, ret)) {
|
||||||
|
field_not_found(msg, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void require_field(const uint8_t *msg, const char *label, char *buffer, uint8_t bufferlen);
|
||||||
|
float require_field_float(const uint8_t *msg, const char *label);
|
||||||
|
uint8_t require_field_uint8_t(const uint8_t *msg, const char *label);
|
||||||
|
int32_t require_field_int32_t(const uint8_t *msg, const char *label);
|
||||||
|
uint16_t require_field_uint16_t(const uint8_t *msg, const char *label);
|
||||||
|
int16_t require_field_int16_t(const uint8_t *msg, const char *label);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void add_field(const char *_label, uint8_t _type, uint8_t _offset, uint8_t length);
|
||||||
|
|
||||||
|
template < typename R >
|
||||||
|
void field_value_for_type_at_offset(const uint8_t *msg, uint8_t type, uint8_t offset, R &ret);
|
||||||
|
|
||||||
|
struct format_field_info { // parsed field information
|
||||||
|
char *label;
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t offset;
|
||||||
|
uint8_t length;
|
||||||
|
};
|
||||||
|
struct format_field_info field_info[LOGREADER_MAX_FIELDS] = {};
|
||||||
|
|
||||||
|
uint8_t next_field;
|
||||||
|
size_t size_for_type_table[52]; // maps field type (e.g. 'f') to e.g 4 bytes
|
||||||
|
|
||||||
|
void parse_format_fields();
|
||||||
|
void init_field_types();
|
||||||
|
void add_field_type(char type, size_t size);
|
||||||
|
uint8_t size_for_type(char type);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct format_field_info *find_field_info(const char *label);
|
||||||
|
|
||||||
|
struct log_Format f; // the format we are a parser for
|
||||||
|
~MsgHandler();
|
||||||
|
|
||||||
|
void location_from_msg(uint8_t *msg, Location &loc, const char *label_lat,
|
||||||
|
const char *label_long, const char *label_alt);
|
||||||
|
|
||||||
|
void ground_vel_from_msg(uint8_t *msg, Vector3f &vel, const char *label_speed,
|
||||||
|
const char *label_course, const char *label_vz);
|
||||||
|
|
||||||
|
void attitude_from_msg(uint8_t *msg, Vector3f &att, const char *label_roll,
|
||||||
|
const char *label_pitch, const char *label_yaw);
|
||||||
|
void field_not_found(const uint8_t *msg, const char *label);
|
||||||
|
};
|
||||||
|
|
||||||
|
template < typename R >
|
||||||
|
bool MsgHandler::field_value(const uint8_t *msg, const char *label, R &ret)
|
||||||
|
{
|
||||||
|
struct format_field_info *info = find_field_info(label);
|
||||||
|
if (info == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t offset = info->offset;
|
||||||
|
if (offset == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
field_value_for_type_at_offset(msg, info->type, offset, ret);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename R >
|
||||||
|
inline void MsgHandler::field_value_for_type_at_offset(const uint8_t *msg, uint8_t type,
|
||||||
|
uint8_t offset, R &ret)
|
||||||
|
{
|
||||||
|
/* we register the types - add_field_type - so can we do without
|
||||||
|
* this switch statement somehow? */
|
||||||
|
switch (type) {
|
||||||
|
case 'B':
|
||||||
|
ret = (R)(((uint8_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
case 'h':
|
||||||
|
ret = (R)(((int16_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
ret = (R)(((uint16_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
ret = (R)(((uint16_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
ret = (R)(((float *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
case 'E':
|
||||||
|
ret = (R)(((uint32_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
case 'e':
|
||||||
|
ret = (R)(((int32_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
ret = (R)(((int64_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
ret = (R)(((uint64_t *)&msg[offset])[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
::printf("Unhandled format type (%c)\n", type);
|
||||||
|
::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
76
sololink/flightcode/dataflash_logger/analyzer_util.cpp
Normal file
76
sololink/flightcode/dataflash_logger/analyzer_util.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include "analyzer_util.h"
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
void format_timestamp(char *buf, const uint8_t buflen, const uint64_t T)
|
||||||
|
{
|
||||||
|
struct tm *tmp;
|
||||||
|
time_t t = T / 1000000;
|
||||||
|
tmp = localtime(&t);
|
||||||
|
::strftime(buf, buflen, "%Y%m%d%H%M%S", tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t now()
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
return tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
|
||||||
|
}
|
||||||
|
|
||||||
|
double vec_len(double vec[3])
|
||||||
|
{
|
||||||
|
return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double earthradius() // in metres
|
||||||
|
{
|
||||||
|
return 6371000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
double wrap_valid_longitude(const double longitude)
|
||||||
|
{
|
||||||
|
if (longitude < 180.0f) {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 180 - (longitude - 180);
|
||||||
|
// return (((longitude + 180.0) % 360.0) -180.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
void gps_newpos(const double orig_lat, const double orig_lon, const double bearing,
|
||||||
|
const double distance, double &dest_lat, double &dest_lon)
|
||||||
|
{
|
||||||
|
double origin_lat_rad = deg_to_rad(orig_lat);
|
||||||
|
double origin_lon_rad = deg_to_rad(orig_lon);
|
||||||
|
double bearing_rad = deg_to_rad(bearing);
|
||||||
|
|
||||||
|
double dr = distance / earthradius();
|
||||||
|
|
||||||
|
dest_lat =
|
||||||
|
asin(sin(origin_lat_rad) * cos(dr) + cos(origin_lat_rad) * sin(dr) * cos(bearing_rad));
|
||||||
|
dest_lon = origin_lon_rad + atan2(sin(bearing_rad) * sin(dr) * cos(origin_lat_rad),
|
||||||
|
cos(dr) - sin(origin_lat_rad) * sin(origin_lat_rad));
|
||||||
|
dest_lat = rad_to_deg(dest_lat);
|
||||||
|
dest_lon = wrap_valid_longitude(rad_to_deg(dest_lon));
|
||||||
|
}
|
||||||
|
|
||||||
|
// origin_lat in degrees
|
||||||
|
// origin_lon in degrees
|
||||||
|
// bearing in degrees
|
||||||
|
// distance in metres
|
||||||
|
void gps_offset(double orig_lat, double orig_lon, double east, double north, double &dest_lat,
|
||||||
|
double &dest_lon)
|
||||||
|
{
|
||||||
|
double bearing = rad_to_deg(atan2(east, north));
|
||||||
|
double distance = sqrt(east * east + north * north);
|
||||||
|
gps_newpos(orig_lat, orig_lon, bearing, distance, dest_lat, dest_lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
double altitude_from_pressure_delta(double gnd_abs_press, double gnd_temp, double press_abs,
|
||||||
|
double temp UNUSED)
|
||||||
|
{
|
||||||
|
double scaling = press_abs / gnd_abs_press;
|
||||||
|
return 153.8462 * (gnd_temp + 273.15) * (1.0 - exp(0.190259 * log(scaling)));
|
||||||
|
}
|
75
sololink/flightcode/dataflash_logger/analyzer_util.h
Normal file
75
sololink/flightcode/dataflash_logger/analyzer_util.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#ifndef _ANALYZER_UTIL
|
||||||
|
#define _ANALYZER_UTIL
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// from: http://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
double earthradius();
|
||||||
|
|
||||||
|
double wrap_valid_longitude(const double longitude);
|
||||||
|
|
||||||
|
// http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
void gps_newpos(const double orig_lat, const double orig_lon, const double bearing,
|
||||||
|
const double distance, double &dest_lat, double &dest_lon);
|
||||||
|
|
||||||
|
// origin_lat in degrees
|
||||||
|
// origin_lon in degrees
|
||||||
|
// bearing in degrees
|
||||||
|
// distance in metres
|
||||||
|
void gps_offset(double orig_lat, double orig_lon, double east, double north, double &dest_lat,
|
||||||
|
double &dest_lon);
|
||||||
|
|
||||||
|
double altitude_from_pressure_delta(double gnd_abs_press, double gnd_temp, double press_abs,
|
||||||
|
double temp);
|
||||||
|
|
||||||
|
template < typename... Args >
|
||||||
|
std::string string_format(const char *format, const Args... args)
|
||||||
|
{
|
||||||
|
|
||||||
|
int32_t size = snprintf(nullptr, 0, format, args...);
|
||||||
|
if (size < 0) {
|
||||||
|
::fprintf(stderr, "snprintf error (%d): %s\n", size, strerror(errno));
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
size += 1; // Extra space for '\0'
|
||||||
|
std::unique_ptr< char[] > buf(new char[size]);
|
||||||
|
snprintf(buf.get(), size, format, args...);
|
||||||
|
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename... Args >
|
||||||
|
std::string string_format(const std::string format, const Args... args)
|
||||||
|
{
|
||||||
|
return string_format(format.c_str(), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef streq
|
||||||
|
#define streq(x, y) (!strcmp(x, y))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// inline float deg_to_rad(const float deg) {
|
||||||
|
// return deg/M_PI * 180;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// inline float rad_to_deg(const float rad) {
|
||||||
|
// return rad*180/M_PI;
|
||||||
|
// }
|
||||||
|
|
||||||
|
#define deg_to_rad(x) ((x)*M_PI / 180.0f)
|
||||||
|
#define rad_to_deg(x) ((x)*180.0f / M_PI)
|
||||||
|
|
||||||
|
#define is_zero(x) (x < 0.00001)
|
||||||
|
#define is_equal(x, y) (is_zero(fabs((x) - (y))))
|
||||||
|
|
||||||
|
void format_timestamp(char *buf, uint8_t buflen, uint64_t T);
|
||||||
|
|
||||||
|
uint64_t now();
|
||||||
|
|
||||||
|
double vec_len(double vec[3]);
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
#endif // _ANALYZER_UTIL
|
127
sololink/flightcode/dataflash_logger/analyzervehicle_copter.cpp
Normal file
127
sololink/flightcode/dataflash_logger/analyzervehicle_copter.cpp
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
#include "analyzervehicle_copter.h"
|
||||||
|
|
||||||
|
using namespace AnalyzerVehicle;
|
||||||
|
|
||||||
|
bool Copter::param_default(const char *name, float &ret)
|
||||||
|
{
|
||||||
|
if (_frame_type == frame_type_quad) {
|
||||||
|
if (_param_defaults_quad[name]) {
|
||||||
|
ret = _param_defaults_quad[name];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_param_defaults[name]) {
|
||||||
|
ret = _param_defaults[name];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Base::param_default(name, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I think there's an argument for moving the following into Analyzer: */
|
||||||
|
|
||||||
|
bool Copter::is_flying()
|
||||||
|
{
|
||||||
|
if (!is_armed()) {
|
||||||
|
// we hope we're not flying, anyway!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any_motor_running_fast()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Copter::any_motor_running_fast()
|
||||||
|
{
|
||||||
|
for (uint8_t i = 1; i < _num_motors; i++) {
|
||||||
|
if (_servo_output[i] > is_flying_motor_threshold) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set< uint8_t > Copter::motors_clipping_high()
|
||||||
|
{
|
||||||
|
std::set< uint8_t > ret;
|
||||||
|
char label[] = "RCx_MAX";
|
||||||
|
for (uint8_t i = 1; i <= _num_motors; i++) {
|
||||||
|
label[2] = '0' + i;
|
||||||
|
float max;
|
||||||
|
if (param(label, max)) {
|
||||||
|
uint16_t delta = abs((int32_t)_servo_output[i] - (uint16_t)max);
|
||||||
|
if ((float)delta / max < .05) { // within 5%
|
||||||
|
ret.insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set< uint8_t > Copter::motors_clipping_low()
|
||||||
|
{
|
||||||
|
std::set< uint8_t > ret;
|
||||||
|
char label[] = "RCx_MIN";
|
||||||
|
for (uint8_t i = 1; i <= _num_motors; i++) {
|
||||||
|
label[2] = '0' + i;
|
||||||
|
float min;
|
||||||
|
if (param(label, min)) {
|
||||||
|
if (_servo_output[i] < (uint16_t)min ||
|
||||||
|
_servo_output[i] - min < 105) { // FIXME: constant
|
||||||
|
ret.insert(i);
|
||||||
|
}
|
||||||
|
// uint16_t delta = abs((int32_t)_servo_output[i] - (uint16_t)min);
|
||||||
|
// ::fprintf(stderr, "%d delta=%d (%f)\n", i, delta, (float)delta/min);
|
||||||
|
// if ((float)delta/min < .05) {
|
||||||
|
// ::fprintf(stderr, "%d clipping low \n", i);
|
||||||
|
// ret.insert(i);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Copter::set_frame_type(copter_frame_type frame_type)
|
||||||
|
{
|
||||||
|
_frame_type = frame_type;
|
||||||
|
switch (frame_type) {
|
||||||
|
case frame_type_quad:
|
||||||
|
_num_motors = 4;
|
||||||
|
break;
|
||||||
|
case frame_type_y6:
|
||||||
|
_num_motors = 6;
|
||||||
|
break;
|
||||||
|
case invalid:
|
||||||
|
::fprintf(stderr, "Invalid frame type");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Copter::exceeding_angle_max()
|
||||||
|
{
|
||||||
|
float angle_max; // convert from centidegrees
|
||||||
|
if (param_with_defaults("ANGLE_MAX", angle_max)) {
|
||||||
|
angle_max /= 100;
|
||||||
|
if (fabs(att().roll()) > angle_max) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (fabs(att().pitch()) > angle_max) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Copter::set_frame(const char *frame_config_string)
|
||||||
|
{
|
||||||
|
if (strstr(frame_config_string, "QUAD")) {
|
||||||
|
set_frame_type(AnalyzerVehicle::Copter::frame_type_quad);
|
||||||
|
} else if (strstr(frame_config_string, "Y6")) {
|
||||||
|
set_frame_type(AnalyzerVehicle::Copter::frame_type_y6);
|
||||||
|
} else {
|
||||||
|
::fprintf(stderr, "Unknown frame (%s)\n", frame_config_string);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
173
sololink/flightcode/dataflash_logger/common_tool.cpp
Normal file
173
sololink/flightcode/dataflash_logger/common_tool.cpp
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#include "common_tool.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
void Common_Tool::init_config()
|
||||||
|
{
|
||||||
|
if (config_filename == NULL) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
struct stat buf;
|
||||||
|
if (stat(config_filename, &buf) == -1) {
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
if (!streq(default_config_filename, config_filename)) {
|
||||||
|
la_log(LOG_CRIT, "Config file (%s) does not exist", config_filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
la_log(LOG_CRIT, "Failed to stat (%s): %s\n", config_filename, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_config = new INIReader(config_filename);
|
||||||
|
if (_config == NULL) {
|
||||||
|
la_log(LOG_CRIT, "Failed to create config from (%s)\n", config_filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (_config == NULL) {
|
||||||
|
_config = new INIReader("/dev/null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::check_fds_are_empty_after_select(fd_set &fds_read, fd_set &fds_write,
|
||||||
|
fd_set &fds_err, uint8_t nfds)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < nfds; i++) {
|
||||||
|
if (FD_ISSET(i, &fds_read)) {
|
||||||
|
la_log(LOG_ERR, "fds_read not empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (FD_ISSET(i, &fds_write)) {
|
||||||
|
la_log(LOG_ERR, "fds_write not empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (FD_ISSET(i, &fds_err)) {
|
||||||
|
la_log(LOG_ERR, "fds_err not empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::pack_select_fds(fd_set &fds_read UNUSED, fd_set &fds_write UNUSED,
|
||||||
|
fd_set &fds_err UNUSED, uint8_t &nfds UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::handle_select_fds(fd_set &fds_read UNUSED, fd_set &fds_write UNUSED,
|
||||||
|
fd_set &fds_err UNUSED, uint8_t &nfds UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::sighup_received_tophalf()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void Common_Tool::sighup_handler(int signal UNUSED)
|
||||||
|
{
|
||||||
|
_sighup_received = true;
|
||||||
|
}
|
||||||
|
void Common_Tool::do_idle_callbacks()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Common_Tool::select_timeout_us()
|
||||||
|
{
|
||||||
|
return 200000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::select_loop()
|
||||||
|
{
|
||||||
|
fd_set fds_read;
|
||||||
|
fd_set fds_write;
|
||||||
|
fd_set fds_err;
|
||||||
|
uint8_t nfds;
|
||||||
|
while (1) {
|
||||||
|
if (_sighup_received) {
|
||||||
|
sighup_received_tophalf();
|
||||||
|
_sighup_received = false;
|
||||||
|
}
|
||||||
|
/* Wait for a packet, or time out if no packets arrive so we always
|
||||||
|
periodically log status and check for new destinations. Downlink
|
||||||
|
packets are on the order of 100/sec, so the timeout is such that
|
||||||
|
we don't expect timeouts unless solo stops sending packets. We
|
||||||
|
almost always get a packet with a 200 msec timeout, but not with
|
||||||
|
a 100 msec timeout. (Timeouts don't really matter though.) */
|
||||||
|
|
||||||
|
struct timeval timeout;
|
||||||
|
|
||||||
|
FD_ZERO(&fds_read);
|
||||||
|
FD_ZERO(&fds_write);
|
||||||
|
FD_ZERO(&fds_err);
|
||||||
|
nfds = 0;
|
||||||
|
pack_select_fds(fds_read, fds_write, fds_err, nfds);
|
||||||
|
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = select_timeout_us();
|
||||||
|
int res = select(nfds, &fds_read, &fds_write, &fds_err, &timeout);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
unsigned skipped = 0;
|
||||||
|
// if ((skipped = can_log_error()) >= 0)
|
||||||
|
la_log(LOG_ERR, "[%u] select: %s", skipped, strerror(errno));
|
||||||
|
/* this sleep is to avoid soaking the CPU if select starts
|
||||||
|
returning immediately for some reason */
|
||||||
|
/* previous code was not checking errfds; we are now, so
|
||||||
|
perhaps this usleep can go away -pb20150730 */
|
||||||
|
usleep(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
// select timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_select_fds(fds_read, fds_write, fds_err, nfds);
|
||||||
|
|
||||||
|
check_fds_are_empty_after_select(fds_read, fds_write, fds_err, nfds);
|
||||||
|
|
||||||
|
do_idle_callbacks();
|
||||||
|
} /* while (1) */
|
||||||
|
}
|
||||||
|
|
||||||
|
void Common_Tool::parse_fd(Format_Reader *reader, int fd)
|
||||||
|
{
|
||||||
|
char buf[1 << 16];
|
||||||
|
ssize_t buf_start = 0;
|
||||||
|
while (true) {
|
||||||
|
ssize_t bytes_read = read(fd, &buf[buf_start], sizeof(buf) - buf_start);
|
||||||
|
if (bytes_read == -1) {
|
||||||
|
fprintf(stderr, "Read failed: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (bytes_read == 0) {
|
||||||
|
while (reader->feed((uint8_t *)buf, buf_start + bytes_read)) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read += buf_start;
|
||||||
|
ssize_t total_bytes_used = 0;
|
||||||
|
while (total_bytes_used < bytes_read) {
|
||||||
|
ssize_t bytes_used =
|
||||||
|
reader->feed((uint8_t *)(&buf[total_bytes_used]), bytes_read - total_bytes_used);
|
||||||
|
if (bytes_used > bytes_read - total_bytes_used) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
if (bytes_used == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total_bytes_used += bytes_used;
|
||||||
|
}
|
||||||
|
// ::fprintf(stderr, "total_bytes_used = %u\n", total_bytes_used);
|
||||||
|
// ::fprintf(stderr, "bytes_read = %u\n", bytes_read);
|
||||||
|
memcpy(&buf[0], (uint8_t *)(&(buf[total_bytes_used])), bytes_read - total_bytes_used);
|
||||||
|
buf_start = bytes_read - total_bytes_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader->end_of_log();
|
||||||
|
|
||||||
|
// ::fprintf(stderr, "Packet count: %d\n", packet_count);
|
||||||
|
}
|
44
sololink/flightcode/dataflash_logger/common_tool.h
Normal file
44
sololink/flightcode/dataflash_logger/common_tool.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "util.h"
|
||||||
|
#include "analyzer_util.h"
|
||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
#include "format_reader.h"
|
||||||
|
|
||||||
|
class Common_Tool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Common_Tool() : config_filename(default_config_filename)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void sighup_handler(int signal);
|
||||||
|
|
||||||
|
void parse_fd(Format_Reader *reader, int fd);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class INIReader *config()
|
||||||
|
{
|
||||||
|
return _config;
|
||||||
|
};
|
||||||
|
void init_config();
|
||||||
|
const char *default_config_filename = "/etc/sololink.conf";
|
||||||
|
const char *config_filename;
|
||||||
|
|
||||||
|
virtual void sighup_received_tophalf();
|
||||||
|
|
||||||
|
bool _sighup_received = false; // FIXME: scope
|
||||||
|
|
||||||
|
virtual uint32_t select_timeout_us();
|
||||||
|
void select_loop();
|
||||||
|
|
||||||
|
virtual void pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds);
|
||||||
|
virtual void handle_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds);
|
||||||
|
virtual void do_idle_callbacks();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void check_fds_are_empty_after_select(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t nfds);
|
||||||
|
class INIReader *_config = NULL;
|
||||||
|
};
|
477
sololink/flightcode/dataflash_logger/dataflash_logger.cpp
Normal file
477
sololink/flightcode/dataflash_logger/dataflash_logger.cpp
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
#include "dataflash_logger.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h> // for snprintf
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sched.h> // for sched_yield
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "../mavlink/c_library/common/mavlink.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
There are currently two threads: a "network" thread (see handle_message), and the fileio thread (see
|
||||||
|
fileio_task). The network thread takes the data from the packets and packs it into _write_buffers.
|
||||||
|
It then passes the buffers to the fileio thread for writing to disk. The buffers are then passed
|
||||||
|
back to the network thread.
|
||||||
|
|
||||||
|
Notes on thread safety:
|
||||||
|
|
||||||
|
la_log is called by both the network and IO threads. openlog/syslog are thread safe. The la_log
|
||||||
|
code is not designed to be thread safe. That code is commented as such, and in future locking may
|
||||||
|
be required there. The current implementation has races concerning message suppression but fixing
|
||||||
|
these may lead to more complexity than is justified.
|
||||||
|
|
||||||
|
The fileio and network threads in dataflash logger share:
|
||||||
|
- out_fd
|
||||||
|
- _write_buffers[]
|
||||||
|
|
||||||
|
out_fd is not to be touched in the main thread after it has been opened - unless logging has
|
||||||
|
stopped, and all the buffers are owned by the network thread. The fileio thread may only touch
|
||||||
|
out_fd and _write_buffers if logging has started, or it owns buffers.
|
||||||
|
|
||||||
|
The buffers contained in _write_buffers are owned by one thread at a time. Only the owner may
|
||||||
|
modify the buffer. Ownership is transfered by assignment of the owner variable. This will work for
|
||||||
|
two threads, but locking of this transfer will be required if more threads become involved, and the
|
||||||
|
programmer is suitably paranoid.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
void *fileio_task_wrapper(void *instance)
|
||||||
|
{
|
||||||
|
((DataFlash_Logger *)instance)->fileio_task();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFlash_Logger::DataFlash_Logger(MAVLink_Writer *mavlink_writer)
|
||||||
|
: MAVLink_Message_Handler(), _mavlink_writer(mavlink_writer),
|
||||||
|
target_system_id(target_system_id_default), target_component_id(target_component_id_default),
|
||||||
|
seqno_gap_nack_threshold(20)
|
||||||
|
{
|
||||||
|
_current_write_buffer = &_write_buffers[0];
|
||||||
|
pthread_mutex_init(&_write_buffer_mutex, NULL);
|
||||||
|
pthread_cond_init(&_wakeup_fileio_thread, NULL);
|
||||||
|
pthread_create(&_fileio_thread, NULL, fileio_task_wrapper, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::write_buffer_to_disk(const struct df_write_buffer *buffer)
|
||||||
|
{
|
||||||
|
const uint8_t length = MAVLINK_MSG_REMOTE_LOG_DATA_BLOCK_FIELD_DATA_LEN;
|
||||||
|
for (uint16_t j = 0; j < buffer->next_defered_write; j++) {
|
||||||
|
const struct defered_write *some_write = &buffer->defered_writes[j];
|
||||||
|
const uint32_t seqno = some_write->seqno;
|
||||||
|
const uint8_t *data = some_write->data;
|
||||||
|
if (lseek(out_fd, seqno * length, SEEK_SET) == -1) {
|
||||||
|
la_log(LOG_ERR, "mh-dfl: lseek (%u) failed: %s", seqno * length, strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ssize_t written = write(out_fd, data, length);
|
||||||
|
if (written < length) {
|
||||||
|
la_log(LOG_ERR, "mh-dfl: short write: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: write all of the blocks in the buffer to disk, trying to
|
||||||
|
// coalesce writes as we go:
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::fileio_task()
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
// we should never be called while fd == -1.
|
||||||
|
pthread_cond_wait(&_wakeup_fileio_thread, &_write_buffer_mutex);
|
||||||
|
// we don't actually need the mutex:
|
||||||
|
pthread_mutex_unlock(&_write_buffer_mutex);
|
||||||
|
|
||||||
|
df_write_buffer *buffer = next_write_buffer(FILEIO_THREAD);
|
||||||
|
while (buffer != NULL) {
|
||||||
|
write_buffer_to_disk(buffer);
|
||||||
|
buffer->next_defered_write = 0;
|
||||||
|
// hand the buffer back to the net thread:
|
||||||
|
buffer->owner = NETWORK_THREAD;
|
||||||
|
buffer = next_write_buffer(FILEIO_THREAD);
|
||||||
|
fsync(out_fd);
|
||||||
|
|
||||||
|
uint64_t now = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if (now - fileio_last_log_time > 10000000) {
|
||||||
|
// the following isn't quite right given we seek around...
|
||||||
|
la_log(LOG_INFO, "mh-dfl: Current log size: %lu", lseek(out_fd, 0, SEEK_CUR));
|
||||||
|
fileio_last_log_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::sighup_received()
|
||||||
|
{
|
||||||
|
logging_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::idle_tenthHz()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::idle_1Hz()
|
||||||
|
{
|
||||||
|
if (!logging_started) {
|
||||||
|
if (sender_system_id != 0) {
|
||||||
|
// we've previously been logging, so telling the other end
|
||||||
|
// to stop logging may let us restart logging sooner
|
||||||
|
send_stop_logging_packet();
|
||||||
|
}
|
||||||
|
send_start_logging_packet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DataFlash_Logger::idle_10Hz()
|
||||||
|
{
|
||||||
|
if (logging_started) {
|
||||||
|
// if no data packet in 10 seconds then close log
|
||||||
|
uint64_t now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if (now_us - _last_data_packet_time > 10000000) {
|
||||||
|
la_log(LOG_INFO, "mh-dfl: No data packets received for some time (now=%llu last=%llu). "
|
||||||
|
" Closing log. Final log size: %lu",
|
||||||
|
now_us, _last_data_packet_time, lseek(out_fd, 0, SEEK_CUR));
|
||||||
|
logging_stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::idle_100Hz()
|
||||||
|
{
|
||||||
|
push_response_queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::send_response(uint32_t seqno, bool status)
|
||||||
|
{
|
||||||
|
mavlink_message_t msg;
|
||||||
|
mavlink_msg_remote_log_block_status_pack(system_id, component_id, &msg, sender_system_id,
|
||||||
|
sender_component_id, seqno, status);
|
||||||
|
_mavlink_writer->send_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::push_response_queue()
|
||||||
|
{
|
||||||
|
const uint8_t max_packets_to_send = 5;
|
||||||
|
uint8_t packets_sent = 0;
|
||||||
|
while (response_queue_head != response_queue_tail && packets_sent < max_packets_to_send) {
|
||||||
|
send_response(responses[response_queue_tail].seqno, responses[response_queue_tail].status);
|
||||||
|
response_queue_tail++;
|
||||||
|
if (response_queue_tail >= RESPONSE_QUEUE_LENGTH) {
|
||||||
|
response_queue_tail = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataFlash_Logger::configure(INIReader *config)
|
||||||
|
{
|
||||||
|
if (!MAVLink_Message_Handler::configure(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path = config->Get("dflogger", "log_dirpath", "/log/dataflash");
|
||||||
|
_log_directory_path = strdup(path.c_str());
|
||||||
|
if (_log_directory_path == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to do equivalent of 'mkdir -p log_dirpath' here
|
||||||
|
// (i.e. handle multi-level path)
|
||||||
|
if (mkdir(_log_directory_path,
|
||||||
|
S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0) {
|
||||||
|
// this is expected to succeed on the first boot after a factory or
|
||||||
|
// settings reset, then fail with EEXIST each startup after that.
|
||||||
|
if (errno != EEXIST) {
|
||||||
|
la_log(LOG_ERR, "mh-dfl: Failed to create (%s): %s", _log_directory_path,
|
||||||
|
strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target_system_id = config->GetInteger("dflogger", "target_system_id", 0);
|
||||||
|
target_component_id = config->GetInteger("dflogger", "target_component_id", 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataFlash_Logger::make_new_log_filename(char *buffer, uint8_t bufferlen)
|
||||||
|
{
|
||||||
|
uint8_t lastlog_buflen = 128;
|
||||||
|
char lastlog_buf[128];
|
||||||
|
// this was really beautiful, but I don't think SoloLink has an
|
||||||
|
// RTC; it depends on GPS to update its clock (scribbled down
|
||||||
|
// periodically?)
|
||||||
|
|
||||||
|
// time_t t;
|
||||||
|
|
||||||
|
// time(&t);
|
||||||
|
// struct tm *timebits = gmtime(&t);
|
||||||
|
|
||||||
|
// snprintf(buffer, bufferlen, "%s/%04d%02d%02d%02d%02d%02d.BIN",
|
||||||
|
// _log_directory_path,
|
||||||
|
// timebits->tm_year+1900,
|
||||||
|
// timebits->tm_mon+1,
|
||||||
|
// timebits->tm_mday,
|
||||||
|
// timebits->tm_hour,
|
||||||
|
// timebits->tm_min,
|
||||||
|
// timebits->tm_sec);
|
||||||
|
|
||||||
|
memset(lastlog_buf, '\0', lastlog_buflen);
|
||||||
|
snprintf(lastlog_buf, lastlog_buflen, "%s/LASTLOG.TXT", _log_directory_path);
|
||||||
|
int fd;
|
||||||
|
uint32_t num;
|
||||||
|
if ((fd = open(lastlog_buf, O_RDONLY)) == -1) {
|
||||||
|
if (errno != ENOENT) {
|
||||||
|
// what?
|
||||||
|
syslog(LOG_ERR, "Failed to open (%s) for reading: %s", lastlog_buf, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
num = 1;
|
||||||
|
} else {
|
||||||
|
uint8_t numbuf_len = 128;
|
||||||
|
char numbuf[numbuf_len];
|
||||||
|
memset(numbuf, '\0', numbuf_len);
|
||||||
|
int bytes_read = read(fd, numbuf, numbuf_len);
|
||||||
|
close(fd);
|
||||||
|
if (bytes_read == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
num = strtoul(numbuf, NULL, 10);
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fd = open(lastlog_buf, O_WRONLY | O_TRUNC | O_CREAT)) == -1) {
|
||||||
|
// *shrug* We will continue to overwrite, I guess...
|
||||||
|
} else {
|
||||||
|
const uint8_t outsize = 16;
|
||||||
|
char out[outsize];
|
||||||
|
memset(out, '\0', outsize);
|
||||||
|
int towrite = snprintf(out, outsize, "%d\r\n", num);
|
||||||
|
write(fd, out, towrite); // ignore return...
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(buffer, bufferlen, "%s/%d.BIN", _log_directory_path, num);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataFlash_Logger::output_file_open()
|
||||||
|
{
|
||||||
|
const uint8_t filename_length = 64;
|
||||||
|
char filename[filename_length];
|
||||||
|
|
||||||
|
if (!make_new_log_filename(filename, filename_length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0777);
|
||||||
|
if (out_fd == -1) {
|
||||||
|
printf("Failed to open (%s): %s\n", filename, strerror(errno));
|
||||||
|
la_log(LOG_ERR, "mh-dfl: Failed to open (%s): %s", filename, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
la_log(LOG_INFO, "mh-dfl: Opened log file (%s)", filename);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::output_file_close()
|
||||||
|
{
|
||||||
|
close(out_fd);
|
||||||
|
out_fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::queue_response(uint32_t seqno, bool status)
|
||||||
|
{
|
||||||
|
responses[response_queue_head].seqno = seqno;
|
||||||
|
responses[response_queue_head].status = status;
|
||||||
|
response_queue_head++;
|
||||||
|
if (response_queue_head >= RESPONSE_QUEUE_LENGTH) {
|
||||||
|
response_queue_head = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::queue_ack(uint32_t seqno)
|
||||||
|
{
|
||||||
|
queue_response(seqno, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::queue_nack(uint32_t seqno)
|
||||||
|
{
|
||||||
|
queue_response(seqno, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::queue_gap_nacks(uint32_t seqno)
|
||||||
|
{
|
||||||
|
if (seqno <= highest_seqno_seen) {
|
||||||
|
// this packet filled in a gap (or was a dupe)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seqno - highest_seqno_seen > seqno_gap_nack_threshold) {
|
||||||
|
// we've seen some serious disruption, and lots of stuff
|
||||||
|
// is probably going to be lost. Do not bother NACKing
|
||||||
|
// packets here and let the server sort things out
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = highest_seqno_seen + 1; i < seqno; i++) {
|
||||||
|
queue_nack(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataFlash_Logger::logging_start(mavlink_remote_log_data_block_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
sender_system_id = most_recent_sender_system_id;
|
||||||
|
sender_component_id = most_recent_sender_component_id;
|
||||||
|
la_log_unsuppress();
|
||||||
|
la_log(LOG_INFO, "mh-dfl: Starting log, target is (%d/%d), I am (%d/%d)", sender_system_id,
|
||||||
|
sender_component_id, this_system_id, this_component_id);
|
||||||
|
if (!output_file_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logging_started = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::ensure_write_buffers_ownership()
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < _max_write_buffers; i++) {
|
||||||
|
uint8_t count = 0;
|
||||||
|
while (_write_buffers[i].owner != NETWORK_THREAD) {
|
||||||
|
pthread_cond_signal(&_wakeup_fileio_thread);
|
||||||
|
sched_yield();
|
||||||
|
// do we need really a sleep in here?
|
||||||
|
usleep(100000);
|
||||||
|
if (count++ > 100) {
|
||||||
|
la_log(LOG_ERR, "Stuck waiting for buffers to be freed; exitting");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::logging_stop()
|
||||||
|
{
|
||||||
|
logging_started = false;
|
||||||
|
// we need to own all of the _write_buffers[] to ensure the
|
||||||
|
// iothread is not currently using the output filehandle:
|
||||||
|
ensure_write_buffers_ownership();
|
||||||
|
output_file_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::handle_message(uint64_t timestamp, mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
most_recent_sender_system_id = msg.sysid;
|
||||||
|
most_recent_sender_component_id = msg.compid;
|
||||||
|
MAVLink_Message_Handler::handle_message(timestamp, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DataFlash_Logger::df_write_buffer *
|
||||||
|
DataFlash_Logger::next_write_buffer(const DataFlash_Logger::write_buffer_owner owner)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < _max_write_buffers; i++) {
|
||||||
|
if (_write_buffers[i].owner == owner) {
|
||||||
|
return &_write_buffers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::handle_decoded_message(uint64_t T UNUSED,
|
||||||
|
mavlink_remote_log_data_block_t &msg)
|
||||||
|
{
|
||||||
|
if (!logging_started) {
|
||||||
|
if (msg.seqno == 0) {
|
||||||
|
if (!logging_start(msg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t now = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if (now - _last_data_packet_time > 100000) {
|
||||||
|
::la_log(LOG_ERR, "mh-dfl: long time between messages (%ld)", now - _last_data_packet_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_current_write_buffer == NULL) {
|
||||||
|
_current_write_buffer = next_write_buffer(NETWORK_THREAD);
|
||||||
|
if (_current_write_buffer == NULL) {
|
||||||
|
la_log(LOG_ERR, "mh-dfl: no write buffer available");
|
||||||
|
_last_data_packet_time = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct defered_write *defered_write =
|
||||||
|
&_current_write_buffer->defered_writes[_current_write_buffer->next_defered_write++];
|
||||||
|
memcpy(defered_write->data, msg.data, MAVLINK_MSG_REMOTE_LOG_DATA_BLOCK_FIELD_DATA_LEN);
|
||||||
|
defered_write->seqno = msg.seqno;
|
||||||
|
if (_current_write_buffer->next_defered_write >= _df_write_buffer_max_defered_writes) {
|
||||||
|
// this buffer is full. Hand it to the FILEIO thread:
|
||||||
|
_current_write_buffer->owner = FILEIO_THREAD;
|
||||||
|
_current_write_buffer = NULL;
|
||||||
|
// and wake the fileio thread up:
|
||||||
|
pthread_cond_signal(&_wakeup_fileio_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_gettime_us(CLOCK_MONOTONIC) - now > 100000) {
|
||||||
|
::la_log(LOG_ERR, "mh-dfl: long time to write (%ld)",
|
||||||
|
clock_gettime_us(CLOCK_MONOTONIC) - now);
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue an ack for this packet
|
||||||
|
queue_ack(msg.seqno);
|
||||||
|
|
||||||
|
// queue nacks for gaps
|
||||||
|
queue_gap_nacks(msg.seqno);
|
||||||
|
|
||||||
|
if (msg.seqno > highest_seqno_seen) {
|
||||||
|
if (msg.seqno - highest_seqno_seen > 100) {
|
||||||
|
la_log(LOG_ERR, "mh-dfl: large seqno gap: %ld", msg.seqno - highest_seqno_seen);
|
||||||
|
}
|
||||||
|
highest_seqno_seen = msg.seqno;
|
||||||
|
}
|
||||||
|
|
||||||
|
_last_data_packet_time = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::send_start_logging_packet()
|
||||||
|
{
|
||||||
|
send_start_or_stop_logging_packet(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::send_stop_logging_packet()
|
||||||
|
{
|
||||||
|
send_start_or_stop_logging_packet(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger::send_start_or_stop_logging_packet(bool is_start)
|
||||||
|
{
|
||||||
|
mavlink_message_t msg;
|
||||||
|
|
||||||
|
uint8_t system_id = is_start ? target_system_id : sender_system_id;
|
||||||
|
uint8_t component_id = is_start ? target_component_id : sender_component_id;
|
||||||
|
uint32_t magic_number;
|
||||||
|
if (is_start) {
|
||||||
|
la_log(LOG_INFO, "mh-dfl: sending start packet to (%d/%d)", system_id, component_id);
|
||||||
|
magic_number = MAV_REMOTE_LOG_DATA_BLOCK_START;
|
||||||
|
} else {
|
||||||
|
la_log(LOG_INFO, "mh-dfl: sending stop packet to (%d/%d)", system_id, component_id);
|
||||||
|
magic_number = MAV_REMOTE_LOG_DATA_BLOCK_STOP;
|
||||||
|
}
|
||||||
|
mavlink_msg_remote_log_block_status_pack(this_system_id, this_component_id, &msg, system_id,
|
||||||
|
component_id, magic_number, 1);
|
||||||
|
|
||||||
|
_mavlink_writer->send_message(msg);
|
||||||
|
}
|
119
sololink/flightcode/dataflash_logger/dataflash_logger.h
Normal file
119
sololink/flightcode/dataflash_logger/dataflash_logger.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#ifndef DATAFLASH_LOGGER_H
|
||||||
|
#define DATAFLASH_LOGGER_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dataflash_logger
|
||||||
|
*
|
||||||
|
* Receive telemetry (mavlink) via UDP from Solo, and create dataflash log files
|
||||||
|
*
|
||||||
|
* Initiate a remote-dataflash stream
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
#include "mavlink_message_handler.h"
|
||||||
|
#include "mavlink_writer.h"
|
||||||
|
|
||||||
|
#include "../mavlink/c_library/common/mavlink.h"
|
||||||
|
|
||||||
|
class DataFlash_Logger : public MAVLink_Message_Handler
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
void fileio_task();
|
||||||
|
|
||||||
|
DataFlash_Logger(MAVLink_Writer *mavlink_writer);
|
||||||
|
|
||||||
|
enum write_buffer_owner { NETWORK_THREAD, FILEIO_THREAD };
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool configure(INIReader *config);
|
||||||
|
|
||||||
|
void sighup_received();
|
||||||
|
|
||||||
|
void idle_tenthHz();
|
||||||
|
void idle_1Hz();
|
||||||
|
void idle_10Hz();
|
||||||
|
void idle_100Hz();
|
||||||
|
bool logging_start(mavlink_remote_log_data_block_t &msg);
|
||||||
|
void logging_stop();
|
||||||
|
void send_stop_logging_packet();
|
||||||
|
|
||||||
|
bool output_file_open();
|
||||||
|
void output_file_close();
|
||||||
|
|
||||||
|
void ensure_write_buffers_ownership();
|
||||||
|
|
||||||
|
// more, smaller buffers means a lower latency to disk. A total
|
||||||
|
// of 700 defered writes gives about 10 seconds of buffering based
|
||||||
|
// on a 12k transfer rate.
|
||||||
|
static const uint8_t _max_write_buffers = 7;
|
||||||
|
static const uint16_t _df_write_buffer_max_defered_writes = 100;
|
||||||
|
struct defered_write {
|
||||||
|
uint8_t data[MAVLINK_MSG_REMOTE_LOG_DATA_BLOCK_FIELD_DATA_LEN];
|
||||||
|
uint32_t seqno;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct df_write_buffer {
|
||||||
|
struct defered_write defered_writes[_df_write_buffer_max_defered_writes];
|
||||||
|
write_buffer_owner owner = NETWORK_THREAD;
|
||||||
|
uint16_t next_defered_write = 0;
|
||||||
|
} _write_buffers[_max_write_buffers];
|
||||||
|
struct df_write_buffer *_current_write_buffer = NULL;
|
||||||
|
void write_buffer_to_disk(const struct df_write_buffer *buffer);
|
||||||
|
bool write_block_for_seqno(const uint8_t *data, uint32_t seqno);
|
||||||
|
struct df_write_buffer *next_write_buffer(const write_buffer_owner owner);
|
||||||
|
uint64_t fileio_last_log_time = 0;
|
||||||
|
|
||||||
|
pthread_mutex_t _write_buffer_mutex;
|
||||||
|
pthread_cond_t _wakeup_fileio_thread;
|
||||||
|
pthread_t _fileio_thread;
|
||||||
|
|
||||||
|
MAVLink_Writer *_mavlink_writer = NULL;
|
||||||
|
uint8_t this_system_id = 57;
|
||||||
|
uint8_t this_component_id = 57;
|
||||||
|
|
||||||
|
const uint8_t target_system_id_default = 0;
|
||||||
|
const uint8_t target_component_id_default = 0;
|
||||||
|
uint8_t most_recent_sender_system_id;
|
||||||
|
uint8_t most_recent_sender_component_id;
|
||||||
|
uint8_t target_system_id; // who to send our request-for-logs to
|
||||||
|
uint8_t target_component_id; // who to send our request-for-logs to
|
||||||
|
uint8_t sender_system_id = 0; // who the logs areactually coming from
|
||||||
|
uint8_t sender_component_id = 0; // who the logs areactually coming from
|
||||||
|
|
||||||
|
void send_start_logging_packet();
|
||||||
|
void send_start_or_stop_logging_packet(bool is_start);
|
||||||
|
const char *_log_directory_path;
|
||||||
|
int out_fd;
|
||||||
|
bool logging_started = false;
|
||||||
|
|
||||||
|
void handle_message(uint64_t timestamp, mavlink_message_t &msg);
|
||||||
|
void handle_decoded_message(uint64_t T, mavlink_remote_log_data_block_t &msg);
|
||||||
|
|
||||||
|
bool make_new_log_filename(char *buffer, uint8_t bufferlen);
|
||||||
|
|
||||||
|
void send_response(uint32_t seqno, bool status);
|
||||||
|
void push_response_queue();
|
||||||
|
|
||||||
|
#define RESPONSE_QUEUE_LENGTH 128
|
||||||
|
struct packet_status {
|
||||||
|
uint32_t seqno;
|
||||||
|
bool status; // ack == true
|
||||||
|
} responses[RESPONSE_QUEUE_LENGTH];
|
||||||
|
uint8_t response_queue_head = 0;
|
||||||
|
uint8_t response_queue_tail = 0;
|
||||||
|
|
||||||
|
uint32_t highest_seqno_seen = 0;
|
||||||
|
uint64_t _last_data_packet_time = 0;
|
||||||
|
|
||||||
|
/* if we lose > this many packets we do not nack anything in that gap: */
|
||||||
|
uint8_t seqno_gap_nack_threshold;
|
||||||
|
|
||||||
|
void queue_response(uint32_t seqno, bool status);
|
||||||
|
void queue_nack(uint32_t seqno);
|
||||||
|
void queue_ack(uint32_t seqno);
|
||||||
|
void queue_gap_nacks(uint32_t seqno);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,167 @@
|
|||||||
|
#include "dataflash_logger_program.h"
|
||||||
|
|
||||||
|
#include "dataflash_logger.h"
|
||||||
|
#include "heart.h"
|
||||||
|
#include "mavlink_reader.h"
|
||||||
|
#include "telem_forwarder_client.h"
|
||||||
|
#include "telem_serial.h"
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
const char *DataFlash_Logger_Program::program_name()
|
||||||
|
{
|
||||||
|
if (_argv == NULL) {
|
||||||
|
return "[Unknown]";
|
||||||
|
}
|
||||||
|
return _argv[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::usage()
|
||||||
|
{
|
||||||
|
::printf("Usage:\n");
|
||||||
|
::printf("%s [OPTION] [FILE]\n", program_name());
|
||||||
|
::printf(" -c filepath use config file filepath\n");
|
||||||
|
::printf(" -h display usage information\n");
|
||||||
|
::printf(" -d debug mode\n");
|
||||||
|
::printf("\n");
|
||||||
|
::printf("Example: %s\n", program_name());
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFlash_Logger_Program logger;
|
||||||
|
|
||||||
|
void sighup_handler(int signal)
|
||||||
|
{
|
||||||
|
logger.sighup_handler(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::do_idle_callbacks()
|
||||||
|
{
|
||||||
|
reader->do_idle_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::sighup_received_tophalf()
|
||||||
|
{
|
||||||
|
reader->sighup_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t DataFlash_Logger_Program::select_timeout_us()
|
||||||
|
{
|
||||||
|
if (_writer->any_data_to_send()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Common_Tool::select_timeout_us();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds)
|
||||||
|
{
|
||||||
|
client->pack_select_fds(fds_read, fds_write, fds_err, nfds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::do_writer_sends()
|
||||||
|
{
|
||||||
|
client->do_writer_sends();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::handle_select_fds(fd_set &fds_read, fd_set &fds_write,
|
||||||
|
fd_set &fds_err, uint8_t &nfds)
|
||||||
|
{
|
||||||
|
client->handle_select_fds(fds_read, fds_write, fds_err, nfds);
|
||||||
|
|
||||||
|
// FIXME: find a more interesting way of doing this... we should
|
||||||
|
// probably rejig things so that the client is a mavlink_reader
|
||||||
|
// and simply produces mavlink_message_t's itself, rather than us
|
||||||
|
// handing off the a dedicated parser object here.
|
||||||
|
reader->feed(client->_recv_buf, client->_recv_buflen_content);
|
||||||
|
client->_recv_buflen_content = 0;
|
||||||
|
|
||||||
|
// handle data *to* e.g. telem_forwarder
|
||||||
|
do_writer_sends();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::run()
|
||||||
|
{
|
||||||
|
init_config();
|
||||||
|
|
||||||
|
if (!debug_mode) {
|
||||||
|
la_log_syslog_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
la_log(LOG_INFO, "dataflash_logger starting: built " __DATE__ " " __TIME__);
|
||||||
|
signal(SIGHUP, ::sighup_handler);
|
||||||
|
|
||||||
|
reader = new MAVLink_Reader(config());
|
||||||
|
if (reader == NULL) {
|
||||||
|
la_log(LOG_ERR, "Failed to create reader from (%s)\n", config_filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serial_port) {
|
||||||
|
client = new Telem_Serial(_client_recv_buf, sizeof(_client_recv_buf));
|
||||||
|
} else {
|
||||||
|
client = new Telem_Forwarder_Client(_client_recv_buf, sizeof(_client_recv_buf));
|
||||||
|
}
|
||||||
|
client->configure(config());
|
||||||
|
client->init();
|
||||||
|
|
||||||
|
_writer = new MAVLink_Writer(config());
|
||||||
|
_writer->add_client(client);
|
||||||
|
if (_writer == NULL) {
|
||||||
|
la_log(LOG_ERR, "Failed to create writer from (%s)\n", config_filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiate message handlers:
|
||||||
|
DataFlash_Logger *dataflash_logger = new DataFlash_Logger(_writer);
|
||||||
|
if (dataflash_logger != NULL) {
|
||||||
|
reader->add_message_handler(dataflash_logger, "DataFlash_Logger");
|
||||||
|
} else {
|
||||||
|
la_log(LOG_INFO, "Failed to create dataflash logger");
|
||||||
|
}
|
||||||
|
|
||||||
|
Heart *heart = new Heart(_writer);
|
||||||
|
if (heart != NULL) {
|
||||||
|
reader->add_message_handler(heart, "Heart");
|
||||||
|
} else {
|
||||||
|
la_log(LOG_INFO, "Failed to create heart");
|
||||||
|
}
|
||||||
|
|
||||||
|
return select_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataFlash_Logger_Program::parse_arguments(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int opt;
|
||||||
|
_argc = argc;
|
||||||
|
_argv = argv;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "hc:ds")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'h':
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
config_filename = optarg;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
debug_mode = true;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
serial_port = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* main - entry point
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
logger.parse_arguments(argc, argv);
|
||||||
|
logger.run();
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
#include "common_tool.h"
|
||||||
|
|
||||||
|
#include "mavlink_reader.h"
|
||||||
|
#include "mavlink_writer.h"
|
||||||
|
#include "telem_client.h"
|
||||||
|
|
||||||
|
class DataFlash_Logger_Program : public Common_Tool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DataFlash_Logger_Program() : Common_Tool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
void parse_arguments(int argc, char *argv[]);
|
||||||
|
const char *program_name();
|
||||||
|
|
||||||
|
void pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds) override;
|
||||||
|
void handle_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void usage();
|
||||||
|
void sighup_received_tophalf() override;
|
||||||
|
void do_idle_callbacks() override;
|
||||||
|
uint32_t select_timeout_us() override;
|
||||||
|
|
||||||
|
void do_writer_sends();
|
||||||
|
|
||||||
|
MAVLink_Reader *reader;
|
||||||
|
MAVLink_Writer *_writer;
|
||||||
|
|
||||||
|
long _argc = 0;
|
||||||
|
char **_argv = NULL;
|
||||||
|
|
||||||
|
uint8_t _client_recv_buf[512] = {}; // FIXME constant was TELEM_PKT_MAX
|
||||||
|
|
||||||
|
static const uint32_t _client_buflen = 65536; // FIXME constant
|
||||||
|
uint32_t _client_buflen_start = 0;
|
||||||
|
uint32_t _client_buflen_stop = 0;
|
||||||
|
|
||||||
|
Telem_Client *client = NULL;
|
||||||
|
bool debug_mode = false;
|
||||||
|
bool serial_port = false;
|
||||||
|
// uint8_t _writer_buf[_writer_buflen] = { };
|
||||||
|
uint32_t canary = 9876543;
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
#include "dataflash_message_handler.h"
|
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef DATAFLASH_MESSAGE_HANDLER_H
|
||||||
|
#define DATAFLASH_MESSAGE_HANDLER_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dataflash_message_handler
|
||||||
|
*
|
||||||
|
* A base class for objects which process dataflash messages and
|
||||||
|
* possibly send responses
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
#include "message_handler.h"
|
||||||
|
|
||||||
|
class DataFlash_Message_Handler : public Message_Handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void handle_format_message_received(const char *name, const struct log_Format &format,
|
||||||
|
const char *msg) = 0;
|
||||||
|
virtual void handle_message_received(const struct log_Format &format, const uint8_t *msg) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
61
sololink/flightcode/dataflash_logger/format_reader.cpp
Normal file
61
sololink/flightcode/dataflash_logger/format_reader.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "format_reader.h"
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
void Format_Reader::do_idle_callbacks()
|
||||||
|
{
|
||||||
|
uint64_t now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if (next_100hz_time <= now_us) {
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->idle_100Hz();
|
||||||
|
}
|
||||||
|
next_100hz_time += 10000;
|
||||||
|
}
|
||||||
|
if (next_10hz_time <= now_us) {
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->idle_10Hz();
|
||||||
|
}
|
||||||
|
next_10hz_time += 100000;
|
||||||
|
}
|
||||||
|
if (next_1hz_time <= now_us) {
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->idle_1Hz();
|
||||||
|
}
|
||||||
|
next_1hz_time += 1000000;
|
||||||
|
}
|
||||||
|
if (next_tenthhz_time <= now_us) {
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->idle_tenthHz();
|
||||||
|
}
|
||||||
|
next_tenthhz_time += 10000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Format_Reader::add_message_handler(Message_Handler *handler, const char *handler_name)
|
||||||
|
{
|
||||||
|
if (MAX_MESSAGE_HANDLERS - next_message_handler < 2) {
|
||||||
|
la_log(LOG_INFO, "Insufficient message handler slots (MAX=%d) (next=%d)?!",
|
||||||
|
MAX_MESSAGE_HANDLERS, next_message_handler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler->configure(_config)) {
|
||||||
|
la_log(LOG_INFO, "Failed to configure (%s)", handler_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
message_handler[next_message_handler++] = handler;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Format_Reader::clear_message_handlers()
|
||||||
|
{
|
||||||
|
next_message_handler = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Format_Reader::sighup_handler()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->sighup_received();
|
||||||
|
}
|
||||||
|
}
|
49
sololink/flightcode/dataflash_logger/format_reader.h
Normal file
49
sololink/flightcode/dataflash_logger/format_reader.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef FORMAT_READER_H
|
||||||
|
#define FORMAT_READER_H
|
||||||
|
|
||||||
|
#include "INIReader.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include "message_handler.h"
|
||||||
|
|
||||||
|
class Format_Reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Format_Reader(INIReader *config) : _config(config)
|
||||||
|
{
|
||||||
|
uint64_t now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
next_tenthhz_time = now_us;
|
||||||
|
next_1hz_time = now_us;
|
||||||
|
next_10hz_time = now_us;
|
||||||
|
next_100hz_time = now_us;
|
||||||
|
};
|
||||||
|
|
||||||
|
void do_idle_callbacks();
|
||||||
|
|
||||||
|
virtual bool add_message_handler(Message_Handler *handler, const char *handler_name);
|
||||||
|
virtual void clear_message_handlers();
|
||||||
|
|
||||||
|
virtual void sighup_handler();
|
||||||
|
|
||||||
|
virtual uint32_t feed(const uint8_t *buf, const uint32_t len) = 0;
|
||||||
|
|
||||||
|
virtual void end_of_log(){};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
INIReader *_config;
|
||||||
|
|
||||||
|
// FIXME: Scope
|
||||||
|
#define MAX_MESSAGE_HANDLERS 10
|
||||||
|
uint8_t next_message_handler = 0;
|
||||||
|
Message_Handler *message_handler[MAX_MESSAGE_HANDLERS];
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool sighup_received = false;
|
||||||
|
|
||||||
|
uint64_t next_tenthhz_time;
|
||||||
|
uint64_t next_1hz_time;
|
||||||
|
uint64_t next_10hz_time;
|
||||||
|
uint64_t next_100hz_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
39
sololink/flightcode/dataflash_logger/heart.cpp
Normal file
39
sololink/flightcode/dataflash_logger/heart.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#include "heart.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
void Heart::idle_10Hz()
|
||||||
|
{
|
||||||
|
uint64_t now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if (last_heartbeat_time + heartbeat_interval < now_us) {
|
||||||
|
last_heartbeat_time = now_us;
|
||||||
|
beat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Heart::configure(INIReader *config)
|
||||||
|
{
|
||||||
|
if (!MAVLink_Message_Handler::configure(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Heart::beat()
|
||||||
|
{
|
||||||
|
mavlink_message_t msg;
|
||||||
|
|
||||||
|
uint8_t type = MAV_TYPE_GCS;
|
||||||
|
uint8_t autopilot = MAV_AUTOPILOT_INVALID;
|
||||||
|
uint8_t base_mode = 0;
|
||||||
|
uint32_t custom_mode = 0;
|
||||||
|
uint8_t system_status = 0;
|
||||||
|
|
||||||
|
la_log(LOG_DEBUG, "mh-h: sending heartbeat");
|
||||||
|
|
||||||
|
mavlink_msg_heartbeat_pack(system_id, component_id, &msg, type, autopilot, base_mode,
|
||||||
|
custom_mode, system_status);
|
||||||
|
|
||||||
|
_mavlink_writer->send_message(msg);
|
||||||
|
}
|
34
sololink/flightcode/dataflash_logger/heart.h
Normal file
34
sololink/flightcode/dataflash_logger/heart.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef HEART_H
|
||||||
|
#define HEART_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* heart
|
||||||
|
*
|
||||||
|
* Periodically send mavlink heartbeats to our upstream connection
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mavlink_message_handler.h"
|
||||||
|
#include "mavlink_writer.h"
|
||||||
|
|
||||||
|
class Heart : public MAVLink_Message_Handler
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
Heart(MAVLink_Writer *mavlink_writer)
|
||||||
|
: MAVLink_Message_Handler(), _mavlink_writer(mavlink_writer), last_heartbeat_time(0),
|
||||||
|
heartbeat_interval(5000000) // microseconds
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void idle_10Hz();
|
||||||
|
bool configure(INIReader *config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MAVLink_Writer *_mavlink_writer = NULL;
|
||||||
|
uint64_t last_heartbeat_time;
|
||||||
|
const uint32_t heartbeat_interval;
|
||||||
|
void beat();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
82
sololink/flightcode/dataflash_logger/la-log.cpp
Normal file
82
sololink/flightcode/dataflash_logger/la-log.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
// Caution: the functions exposed in here are called by different
|
||||||
|
// threads in the same process.
|
||||||
|
|
||||||
|
LALog lalog;
|
||||||
|
|
||||||
|
// globa functions for convenience
|
||||||
|
void la_log_syslog_open()
|
||||||
|
{
|
||||||
|
lalog.syslog_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void la_log_unsuppress()
|
||||||
|
{
|
||||||
|
lalog.unsupress();
|
||||||
|
}
|
||||||
|
|
||||||
|
void la_log(int priority, const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, format);
|
||||||
|
lalog.log_ap(priority, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LALog::syslog_open()
|
||||||
|
{
|
||||||
|
openlog("dl", LOG_NDELAY, LOG_LOCAL1);
|
||||||
|
|
||||||
|
use_syslog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LALog::log(const int priority, const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, format);
|
||||||
|
log_ap(priority, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LALog::should_suppress()
|
||||||
|
{
|
||||||
|
return _message_count_this_time_period >= _max_messages_per_time_period;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LALog::unsupress()
|
||||||
|
{
|
||||||
|
_suppressing = false;
|
||||||
|
_message_count_this_time_period = 0;
|
||||||
|
_time_period_start = time(NULL);
|
||||||
|
if (_suppressed_message_count) {
|
||||||
|
// hmmm. recursion. hmmm.
|
||||||
|
log(LOG_ERR, "%d messages supressed", _suppressed_message_count);
|
||||||
|
_message_count_this_time_period--; // *cough* let one
|
||||||
|
// *message through
|
||||||
|
// *apart from us....
|
||||||
|
_suppressed_message_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void LALog::log_ap(int priority, const char *format, va_list ap)
|
||||||
|
{
|
||||||
|
if (time(NULL) - _time_period_start > _time_period) {
|
||||||
|
unsupress();
|
||||||
|
}
|
||||||
|
if (should_suppress()) {
|
||||||
|
_suppressing = true;
|
||||||
|
}
|
||||||
|
if (suppressing()) {
|
||||||
|
_suppressed_message_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_message_count_this_time_period++;
|
||||||
|
|
||||||
|
if (use_syslog) {
|
||||||
|
vsyslog(priority, format, ap);
|
||||||
|
} else {
|
||||||
|
vfprintf(stderr, format, ap);
|
||||||
|
fprintf(stderr, "%s", "\n");
|
||||||
|
}
|
||||||
|
}
|
45
sololink/flightcode/dataflash_logger/la-log.h
Normal file
45
sololink/flightcode/dataflash_logger/la-log.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef _LA_LOG_H
|
||||||
|
#define _LA_LOG_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
void la_log_syslog_open();
|
||||||
|
void la_log(int priority, const char *format, ...);
|
||||||
|
void la_log_unsuppress();
|
||||||
|
|
||||||
|
class LALog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LALog() : _time_period_start(time(NULL))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void syslog_open();
|
||||||
|
|
||||||
|
void log(int priority, const char *format, ...);
|
||||||
|
void log_ap(int priority, const char *format, va_list ap);
|
||||||
|
|
||||||
|
void unsupress();
|
||||||
|
bool should_suppress();
|
||||||
|
bool suppressing()
|
||||||
|
{
|
||||||
|
return _suppressing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// really rough rate limiting for log messages. We could keep a
|
||||||
|
// hash here of formats and selectively supress based on format.
|
||||||
|
bool _suppressing = false;
|
||||||
|
const uint8_t _time_period = 5; // seconds
|
||||||
|
const uint8_t _max_messages_per_time_period = 10;
|
||||||
|
uint32_t _suppressed_message_count = 0;
|
||||||
|
uint8_t _message_count_this_time_period = 0;
|
||||||
|
time_t _time_period_start;
|
||||||
|
|
||||||
|
bool use_syslog = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
119
sololink/flightcode/dataflash_logger/mavlink_message_handler.cpp
Normal file
119
sololink/flightcode/dataflash_logger/mavlink_message_handler.cpp
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#include "mavlink_message_handler.h"
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
// void MAVLink_Message_Handler::send_message_to_telem_forwarder(mavlink_message_t &msg)
|
||||||
|
// {
|
||||||
|
// uint8_t sendbuf[512];
|
||||||
|
|
||||||
|
// uint16_t messageLen = mavlink_msg_to_send_buffer((uint8_t*)sendbuf,&msg);
|
||||||
|
|
||||||
|
// if (sendto(_fd_telem_forwarder, sendbuf, messageLen, 0, (struct sockaddr
|
||||||
|
// *)_sa_telemetry_forwarder, sizeof(struct sockaddr)) == -1) {
|
||||||
|
// la_log(LOG_INFO, "Failed sendto: %s", strerror(errno));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void MAVLink_Message_Handler::handle_message(uint64_t timestamp, mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
// ::fprintf(stderr, "msg.msgid=%u\n", msg.msgid);
|
||||||
|
switch (msg.msgid) {
|
||||||
|
case MAVLINK_MSG_ID_AHRS2: {
|
||||||
|
mavlink_ahrs2_t decoded;
|
||||||
|
mavlink_msg_ahrs2_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_ATTITUDE: {
|
||||||
|
mavlink_attitude_t decoded;
|
||||||
|
mavlink_msg_attitude_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_EKF_STATUS_REPORT: {
|
||||||
|
mavlink_ekf_status_report_t decoded;
|
||||||
|
mavlink_msg_ekf_status_report_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: {
|
||||||
|
mavlink_global_position_int_t decoded;
|
||||||
|
mavlink_msg_global_position_int_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_GPS_RAW_INT: {
|
||||||
|
mavlink_gps_raw_int_t decoded;
|
||||||
|
mavlink_msg_gps_raw_int_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_HEARTBEAT: {
|
||||||
|
mavlink_heartbeat_t decoded;
|
||||||
|
mavlink_msg_heartbeat_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_MOUNT_STATUS: {
|
||||||
|
mavlink_mount_status_t decoded;
|
||||||
|
mavlink_msg_mount_status_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_NAV_CONTROLLER_OUTPUT: {
|
||||||
|
mavlink_nav_controller_output_t decoded;
|
||||||
|
mavlink_msg_nav_controller_output_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_PARAM_VALUE: {
|
||||||
|
mavlink_param_value_t decoded;
|
||||||
|
mavlink_msg_param_value_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_REMOTE_LOG_DATA_BLOCK: {
|
||||||
|
mavlink_remote_log_data_block_t decoded;
|
||||||
|
mavlink_msg_remote_log_data_block_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_SCALED_PRESSURE: {
|
||||||
|
mavlink_scaled_pressure_t decoded;
|
||||||
|
mavlink_msg_scaled_pressure_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_SCALED_PRESSURE2: {
|
||||||
|
mavlink_scaled_pressure2_t decoded;
|
||||||
|
mavlink_msg_scaled_pressure2_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_SERVO_OUTPUT_RAW: {
|
||||||
|
mavlink_servo_output_raw_t decoded;
|
||||||
|
mavlink_msg_servo_output_raw_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_STATUSTEXT: {
|
||||||
|
mavlink_statustext_t decoded;
|
||||||
|
mavlink_msg_statustext_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_SYS_STATUS: {
|
||||||
|
mavlink_sys_status_t decoded;
|
||||||
|
mavlink_msg_sys_status_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MAVLINK_MSG_ID_VFR_HUD: {
|
||||||
|
mavlink_vfr_hud_t decoded;
|
||||||
|
mavlink_msg_vfr_hud_decode(&msg, &decoded);
|
||||||
|
handle_decoded_message(timestamp, decoded);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
#ifndef MAVLINK_MESSAGE_HANDLER_H
|
||||||
|
#define MAVLINK_MESSAGE_HANDLER_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mavlink_message_handler
|
||||||
|
*
|
||||||
|
* A base class for objects which process mavlink messages and
|
||||||
|
* possibly send responses
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "INIReader.h"
|
||||||
|
#include "../mavlink/c_library/ardupilotmega/mavlink.h"
|
||||||
|
#include "../mavlink/c_library/common/mavlink.h"
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "message_handler.h"
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
class MAVLink_Message_Handler : public Message_Handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MAVLink_Message_Handler() : Message_Handler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool configure(INIReader *config)
|
||||||
|
{
|
||||||
|
system_id = config->GetInteger("dflogger", "system_id", 254);
|
||||||
|
component_id = config->GetInteger("dflogger", "component_id", 17);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void handle_message(uint64_t timestamp, mavlink_message_t &msg);
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_ahrs2_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_attitude_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_ekf_status_report_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_gps_raw_int_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED,
|
||||||
|
mavlink_global_position_int_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_heartbeat_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_mount_status_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED,
|
||||||
|
mavlink_nav_controller_output_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_param_value_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED,
|
||||||
|
mavlink_remote_log_data_block_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_scaled_pressure_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_scaled_pressure2_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_servo_output_raw_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_sys_status_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_statustext_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void handle_decoded_message(uint64_t T UNUSED, mavlink_vfr_hud_t &msg UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t system_id;
|
||||||
|
uint8_t component_id;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
82
sololink/flightcode/dataflash_logger/mavlink_reader.cpp
Normal file
82
sololink/flightcode/dataflash_logger/mavlink_reader.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include "mavlink_reader.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h> // for perror
|
||||||
|
#include <stdlib.h> // for abort
|
||||||
|
|
||||||
|
#include <unistd.h> // for usleep
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "analyzer_util.h"
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
/* This is used to prevent swamping the log with error messages if
|
||||||
|
something unexpected happens.
|
||||||
|
Returns -1 if we cannot log an error now, or returns the number of
|
||||||
|
messages skipped due to rate limiting if we can, i.e. a return of
|
||||||
|
2 means log, and we have skipped 2 messages due to rate limiting. */
|
||||||
|
int MAVLink_Reader::can_log_error()
|
||||||
|
{
|
||||||
|
unsigned ret_val;
|
||||||
|
uint64_t now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
if ((now_us - err_time_us) < err_interval_us) {
|
||||||
|
/* can't log */
|
||||||
|
err_skipped++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* yes; say we can and set err_time_us assuming we do log something */
|
||||||
|
err_time_us = now_us;
|
||||||
|
ret_val = err_skipped;
|
||||||
|
err_skipped = 0;
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAVLink_Reader::handle_message_received(uint64_t timestamp, mavlink_message_t msg)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
((MAVLink_Message_Handler *)message_handler[i])->handle_message(timestamp, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MAVLink_Reader::feed(const uint8_t *buf, const uint32_t len)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
// ::printf("Read (%d)\n", i);
|
||||||
|
if (is_tlog() && !done_timestamp) {
|
||||||
|
timestamp <<= 8;
|
||||||
|
timestamp |= (uint8_t)(buf[i]);
|
||||||
|
if (timestamp_offset++ == 7) {
|
||||||
|
done_timestamp = true;
|
||||||
|
// ::printf("timestamp (%lu)\n", timestamp);
|
||||||
|
timestamp_offset = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mavlink_parse_char(MAVLINK_COMM_0, buf[i], &mav_msg, &mav_status)) {
|
||||||
|
if (!is_tlog()) {
|
||||||
|
timestamp = now();
|
||||||
|
}
|
||||||
|
// printf("message received at %d (parse_errors=%u)\n", i, mav_status.parse_error);
|
||||||
|
handle_message_received(timestamp, mav_msg);
|
||||||
|
packet_count++;
|
||||||
|
done_timestamp = false;
|
||||||
|
timestamp = 0;
|
||||||
|
} else {
|
||||||
|
// printf(" %u state: %u\n", i, mav_status.parse_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len; // at the moment we parse everything we receive
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAVLink_Reader::end_of_log()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < next_message_handler; i++) {
|
||||||
|
message_handler[i]->end_of_log(packet_count);
|
||||||
|
}
|
||||||
|
}
|
61
sololink/flightcode/dataflash_logger/mavlink_reader.h
Normal file
61
sololink/flightcode/dataflash_logger/mavlink_reader.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#ifndef MAVLINK_READER_H
|
||||||
|
#define MAVLINK_READER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "INIReader.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include "mavlink_message_handler.h"
|
||||||
|
|
||||||
|
#include "format_reader.h"
|
||||||
|
|
||||||
|
/* A mavlink packet should be limited to 6+255+2 = 263 bytes
|
||||||
|
6 byte header, 255 max bytes in payload, 2 byte crc */
|
||||||
|
#define TELEM_PKT_MAX 512
|
||||||
|
|
||||||
|
class MAVLink_Reader : public Format_Reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MAVLink_Reader(INIReader *config)
|
||||||
|
: Format_Reader(config), err_skipped(0), err_time_us(0), /* last time we logged */
|
||||||
|
err_interval_us(1000000), /* once per second max */
|
||||||
|
_is_tlog(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t feed(const uint8_t *buf, const uint32_t len) override;
|
||||||
|
|
||||||
|
void set_is_tlog(bool value)
|
||||||
|
{
|
||||||
|
_is_tlog = value;
|
||||||
|
}
|
||||||
|
bool is_tlog()
|
||||||
|
{
|
||||||
|
return _is_tlog;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void end_of_log() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int can_log_error();
|
||||||
|
|
||||||
|
void handle_message_received(uint64_t timestamp, mavlink_message_t msg);
|
||||||
|
|
||||||
|
uint16_t err_skipped;
|
||||||
|
uint64_t err_time_us;
|
||||||
|
uint64_t err_interval_us;
|
||||||
|
|
||||||
|
uint32_t packet_count = 0;
|
||||||
|
uint8_t timestamp_offset = 0;
|
||||||
|
bool done_timestamp = false;
|
||||||
|
uint64_t timestamp = 0;
|
||||||
|
mavlink_message_t mav_msg;
|
||||||
|
mavlink_status_t mav_status;
|
||||||
|
|
||||||
|
bool _is_tlog;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
29
sololink/flightcode/dataflash_logger/mavlink_writer.cpp
Normal file
29
sololink/flightcode/dataflash_logger/mavlink_writer.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "mavlink_writer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void MAVLink_Writer::add_client(Telem_Client *client)
|
||||||
|
{
|
||||||
|
clients.push_back(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAVLink_Writer::send_message(const mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
// std::for_each(clients.begin(), clients.end(), send_message(msg));
|
||||||
|
std::for_each(clients.begin(), clients.end(), [msg](Telem_Client *c) { c->send_message(msg); });
|
||||||
|
// for (std::vector<Telem_Client *>::iterator it = clients.begin();
|
||||||
|
// it != clients.end();
|
||||||
|
// it++) {
|
||||||
|
// (*it)->send_message(msg);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAVLink_Writer::any_data_to_send()
|
||||||
|
{
|
||||||
|
for (std::vector< Telem_Client * >::iterator it = clients.begin(); it != clients.end(); it++) {
|
||||||
|
if ((*it)->any_data_to_send()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
27
sololink/flightcode/dataflash_logger/mavlink_writer.h
Normal file
27
sololink/flightcode/dataflash_logger/mavlink_writer.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _MAVLINK_WRITER_H
|
||||||
|
#define _MAVLINK_WRITER_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../mavlink/c_library/ardupilotmega/mavlink.h"
|
||||||
|
#include "INIReader.h"
|
||||||
|
#include "telem_client.h"
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
class MAVLink_Writer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MAVLink_Writer(INIReader *config UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void send_message(const mavlink_message_t &msg);
|
||||||
|
|
||||||
|
void add_client(Telem_Client *);
|
||||||
|
bool any_data_to_send();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector< Telem_Client * > clients;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
39
sololink/flightcode/dataflash_logger/message_handler.h
Normal file
39
sololink/flightcode/dataflash_logger/message_handler.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef MESSAGE_HANDLER_H
|
||||||
|
#define MESSAGE_HANDLER_H
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
class Message_Handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
friend class Format_Reader;
|
||||||
|
|
||||||
|
virtual bool configure(INIReader *config UNUSED)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void sighup_received()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void end_of_log(uint32_t packet_count UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void idle_tenthHz()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void idle_1Hz()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void idle_10Hz()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void idle_100Hz()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
13
sololink/flightcode/dataflash_logger/telem_client.cpp
Normal file
13
sololink/flightcode/dataflash_logger/telem_client.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include "telem_client.h"
|
||||||
|
|
||||||
|
uint32_t Telem_Client::send_buffer_used()
|
||||||
|
{
|
||||||
|
if (_send_buf_stop >= _send_buf_start) {
|
||||||
|
return _send_buf_stop - _send_buf_start;
|
||||||
|
}
|
||||||
|
return send_buf_size() - _send_buf_start + _send_buf_stop;
|
||||||
|
}
|
||||||
|
uint32_t Telem_Client::send_buffer_space_free()
|
||||||
|
{
|
||||||
|
return send_buf_size() - send_buffer_used();
|
||||||
|
}
|
44
sololink/flightcode/dataflash_logger/telem_client.h
Normal file
44
sololink/flightcode/dataflash_logger/telem_client.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef _TELEM_CLIENT_H
|
||||||
|
#define _TELEM_CLIENT_H
|
||||||
|
|
||||||
|
#include "../mavlink/c_library/ardupilotmega/mavlink.h"
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
class Telem_Client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Telem_Client(uint8_t *recv_buf, uint32_t recv_buflen)
|
||||||
|
: _recv_buf(recv_buf), _recv_buflen(recv_buflen)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds) = 0;
|
||||||
|
virtual void configure(INIReader *config) = 0;
|
||||||
|
virtual void handle_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err,
|
||||||
|
uint8_t &nfds) = 0;
|
||||||
|
|
||||||
|
virtual void init() = 0;
|
||||||
|
|
||||||
|
virtual void do_writer_sends() = 0;
|
||||||
|
virtual bool send_message(const mavlink_message_t &message) = 0;
|
||||||
|
virtual bool any_data_to_send() = 0;
|
||||||
|
|
||||||
|
virtual uint32_t send_buffer_space_free();
|
||||||
|
virtual uint32_t send_buffer_used();
|
||||||
|
|
||||||
|
uint32_t _send_buf_start = 0;
|
||||||
|
uint32_t _send_buf_stop = 0;
|
||||||
|
|
||||||
|
// FIXME: scope
|
||||||
|
uint8_t *_recv_buf; // receive buffer
|
||||||
|
uint32_t _recv_buflen; // receive buffer len
|
||||||
|
uint32_t _recv_buflen_content = 0;
|
||||||
|
|
||||||
|
virtual uint32_t send_buf_size() const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
174
sololink/flightcode/dataflash_logger/telem_forwarder_client.cpp
Normal file
174
sololink/flightcode/dataflash_logger/telem_forwarder_client.cpp
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#include "telem_forwarder_client.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
// #include <sys/socket.h>
|
||||||
|
// #include <netinet/in.h>
|
||||||
|
// #include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::pack_select_fds(fd_set &fds_read, fd_set &fds_write UNUSED,
|
||||||
|
fd_set &fds_err, uint8_t &nfds)
|
||||||
|
{
|
||||||
|
FD_SET(fd_telem_forwarder, &fds_read);
|
||||||
|
FD_SET(fd_telem_forwarder, &fds_err);
|
||||||
|
|
||||||
|
if (fd_telem_forwarder >= nfds) {
|
||||||
|
nfds = fd_telem_forwarder + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::handle_select_fds(fd_set &fds_read, fd_set &fds_write UNUSED,
|
||||||
|
fd_set &fds_err, uint8_t &nfds UNUSED)
|
||||||
|
{
|
||||||
|
/* check for packets from telem_forwarder */
|
||||||
|
if (FD_ISSET(fd_telem_forwarder, &fds_err)) {
|
||||||
|
FD_CLR(fd_telem_forwarder, &fds_err);
|
||||||
|
la_log(LOG_ERR, "select(fd_telem_forwarder): %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(fd_telem_forwarder, &fds_read)) {
|
||||||
|
FD_CLR(fd_telem_forwarder, &fds_read);
|
||||||
|
_recv_buflen_content = handle_recv();
|
||||||
|
// ::fprintf(stderr, "received %u bytes\n", _buflen_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create_and_bind - create a socket and bind it to a local UDP port
|
||||||
|
*
|
||||||
|
* Used to create the socket on the upstream side that receives from and sends
|
||||||
|
* to telem_forwarder
|
||||||
|
*
|
||||||
|
* Returns fd on success, -1 on error.
|
||||||
|
*/
|
||||||
|
void Telem_Forwarder_Client::create_and_bind()
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
|
||||||
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("socket");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
sa.sin_port = 0; // we don't care what our port is
|
||||||
|
|
||||||
|
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
||||||
|
perror("bind");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_telem_forwarder = fd;
|
||||||
|
} /* create_and_bind */
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::pack_telem_forwarder_sockaddr(INIReader *config)
|
||||||
|
{
|
||||||
|
// uint16_t tf_port = config->GetInteger("solo", "telem_forward_port", 14560);
|
||||||
|
// std::string ip = config->Get("solo", "soloIp", "10.1.1.10");
|
||||||
|
uint16_t tf_port = config->GetInteger("solo", "telem_forward_port", 14560);
|
||||||
|
std::string ip = config->Get("solo", "soloIp", "127.0.0.1");
|
||||||
|
|
||||||
|
la_log(LOG_INFO, "df-tfc: connecting to telem-forwarder at %s:%u", ip.c_str(), tf_port);
|
||||||
|
memset(&sa_tf, 0, sizeof(sa_tf));
|
||||||
|
sa_tf.sin_family = AF_INET;
|
||||||
|
// sa_tf.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
inet_aton(ip.c_str(), &sa_tf.sin_addr); // useful for debugging
|
||||||
|
sa_tf.sin_port = htons(tf_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Telem_Forwarder_Client::sane_telem_forwarder_packet(uint8_t *pkt, uint16_t pktlen)
|
||||||
|
{
|
||||||
|
if (sa.sin_addr.s_addr != sa_tf.sin_addr.s_addr) {
|
||||||
|
la_log(LOG_ERR, "received packet not from solo (0x%08x)", sa.sin_addr.s_addr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pktlen < 8) {
|
||||||
|
la_log(LOG_ERR, "received runt packet (%d bytes)", pktlen);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pkt[0] != 254) {
|
||||||
|
la_log(LOG_ERR, "received bad magic (0x%02x)", pkt[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pkt[1] != (pktlen - 8)) {
|
||||||
|
la_log(LOG_ERR, "inconsistent length (%u, %u)", pkt[1], pktlen);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Telem_Forwarder_Client::handle_recv()
|
||||||
|
{
|
||||||
|
// ::printf("Receiving packet into position %u\n", _buflen_content);
|
||||||
|
/* packet from telem_forwarder */
|
||||||
|
socklen_t sa_len = sizeof(sa);
|
||||||
|
uint16_t res =
|
||||||
|
recvfrom(fd_telem_forwarder, &_recv_buf[_recv_buflen_content],
|
||||||
|
_recv_buflen - _recv_buflen_content, 0, (struct sockaddr *)&sa, &sa_len);
|
||||||
|
|
||||||
|
/* We get one mavlink packet per udp datagram. Sanity checks here
|
||||||
|
are: must be from solo's IP and have a valid mavlink header. */
|
||||||
|
// FIXME: we don't necessarily get just one packet/buffer!
|
||||||
|
// ::fprintf(stderr, "handle_recv\n");
|
||||||
|
if (!sane_telem_forwarder_packet(_recv_buf, res)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Telem_Forwarder_Client::send_message(const mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
if (send_buffer_space_free() < 1) {
|
||||||
|
// dropped_packets++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(&_send_buf[_send_buf_stop++], (char *)&msg, sizeof(msg));
|
||||||
|
if (_send_buf_stop >= send_buf_size()) {
|
||||||
|
_send_buf_stop = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::do_writer_sends()
|
||||||
|
{
|
||||||
|
char buf[1024]; // large enough...
|
||||||
|
|
||||||
|
while (_send_buf_start != _send_buf_stop) {
|
||||||
|
mavlink_message_t &msg = _send_buf[_send_buf_start];
|
||||||
|
uint16_t messageLen = mavlink_msg_to_send_buffer((uint8_t *)buf, &msg);
|
||||||
|
|
||||||
|
int32_t bytes_sent = sendto(fd_telem_forwarder, buf, messageLen, 0,
|
||||||
|
(struct sockaddr *)&sa_tf, sizeof(struct sockaddr));
|
||||||
|
if (bytes_sent == -1) {
|
||||||
|
la_log(LOG_INFO, "Failed sendto: %s", strerror(errno));
|
||||||
|
// we drop the message anyway!
|
||||||
|
}
|
||||||
|
_send_buf_start++;
|
||||||
|
if (_send_buf_start >= send_buf_size()) {
|
||||||
|
_send_buf_start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::configure(INIReader *config)
|
||||||
|
{
|
||||||
|
/* prepare sockaddr used to contact telem_forwarder */
|
||||||
|
pack_telem_forwarder_sockaddr(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Forwarder_Client::init()
|
||||||
|
{
|
||||||
|
/* Prepare a port to receive and send data to/from telem_forwarder */
|
||||||
|
/* does not return on failure */
|
||||||
|
create_and_bind();
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "telem_client.h"
|
||||||
|
|
||||||
|
class Telem_Forwarder_Client : public Telem_Client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Telem_Forwarder_Client(uint8_t *recv_buf, uint32_t recv_buflen)
|
||||||
|
: Telem_Client(recv_buf, recv_buflen)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t handle_recv();
|
||||||
|
void init() override;
|
||||||
|
|
||||||
|
void configure(INIReader *config);
|
||||||
|
void pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err, uint8_t &nfds);
|
||||||
|
void handle_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err, uint8_t &nfds);
|
||||||
|
|
||||||
|
void do_writer_sends();
|
||||||
|
bool send_message(const mavlink_message_t &msg);
|
||||||
|
bool any_data_to_send()
|
||||||
|
{
|
||||||
|
return _send_buf_start != _send_buf_stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd_telem_forwarder = -1;
|
||||||
|
|
||||||
|
struct sockaddr_in sa; // our send-from address
|
||||||
|
struct sockaddr_in sa_tf; /* telem_forwarder's address */
|
||||||
|
|
||||||
|
void create_and_bind();
|
||||||
|
void pack_telem_forwarder_sockaddr(INIReader *config);
|
||||||
|
bool sane_telem_forwarder_packet(uint8_t *pkt, uint16_t bpktlen);
|
||||||
|
|
||||||
|
/* send buffer stuff: */
|
||||||
|
mavlink_message_t _send_buf[256]; // way too bug?
|
||||||
|
uint32_t send_buf_size() const
|
||||||
|
{
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
};
|
217
sololink/flightcode/dataflash_logger/telem_serial.cpp
Normal file
217
sololink/flightcode/dataflash_logger/telem_serial.cpp
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
#include "telem_serial.h"
|
||||||
|
|
||||||
|
#include "la-log.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
|
||||||
|
void Telem_Serial::init()
|
||||||
|
{
|
||||||
|
open_serial_port();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Serial::pack_select_fds(fd_set &fds_read, fd_set &fds_write UNUSED, fd_set &fds_err,
|
||||||
|
uint8_t &nfds)
|
||||||
|
{
|
||||||
|
FD_SET(fd, &fds_read);
|
||||||
|
FD_SET(fd, &fds_err);
|
||||||
|
|
||||||
|
if (fd >= nfds) {
|
||||||
|
nfds = fd + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Serial::handle_select_fds(fd_set &fds_read, fd_set &fds_write UNUSED, fd_set &fds_err,
|
||||||
|
uint8_t &nfds UNUSED)
|
||||||
|
{
|
||||||
|
/* check for packets from telem_forwarder */
|
||||||
|
if (FD_ISSET(fd, &fds_err)) {
|
||||||
|
FD_CLR(fd, &fds_err);
|
||||||
|
la_log(LOG_ERR, "select(%d): %s", fd, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(fd, &fds_read)) {
|
||||||
|
FD_CLR(fd, &fds_read);
|
||||||
|
_recv_buflen_content = handle_read();
|
||||||
|
// ::fprintf(stderr, "received %u bytes\n", _buflen_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// from serial_setup in telem_forward
|
||||||
|
void Telem_Serial::open_serial_port()
|
||||||
|
{
|
||||||
|
fd = open(serialPortName.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||||
|
|
||||||
|
if (fd < 0) {
|
||||||
|
la_log(LOG_ERR, "unable to open serial port %s", serialPortName.c_str());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure port for 8N1 transmission
|
||||||
|
struct termios options;
|
||||||
|
|
||||||
|
tcgetattr(fd, &options); // Gets the current options for the port
|
||||||
|
// Set the output baud rate
|
||||||
|
switch (serialBaud) {
|
||||||
|
case 1200:
|
||||||
|
cfsetspeed(&options, B1200);
|
||||||
|
break;
|
||||||
|
case 2400:
|
||||||
|
cfsetspeed(&options, B2400);
|
||||||
|
break;
|
||||||
|
case 4800:
|
||||||
|
cfsetspeed(&options, B4800);
|
||||||
|
break;
|
||||||
|
case 9600:
|
||||||
|
cfsetspeed(&options, B9600);
|
||||||
|
break;
|
||||||
|
case 19200:
|
||||||
|
cfsetspeed(&options, B19200);
|
||||||
|
break;
|
||||||
|
case 38400:
|
||||||
|
cfsetspeed(&options, B38400);
|
||||||
|
break;
|
||||||
|
case 57600:
|
||||||
|
cfsetspeed(&options, B57600);
|
||||||
|
break;
|
||||||
|
case 115200:
|
||||||
|
cfsetspeed(&options, B115200);
|
||||||
|
break;
|
||||||
|
case 500000:
|
||||||
|
cfsetspeed(&options, B500000);
|
||||||
|
break;
|
||||||
|
case 921600:
|
||||||
|
cfsetspeed(&options, B921600);
|
||||||
|
break;
|
||||||
|
case 1500000:
|
||||||
|
cfsetspeed(&options, B1500000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
syslog(LOG_ERR, "unsupported baud rate %d", serialBaud);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
options.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
|
||||||
|
options.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OPOST);
|
||||||
|
options.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
|
||||||
|
options.c_cflag &= ~(CSIZE | PARENB);
|
||||||
|
options.c_cflag |= (CS8 | CLOCAL);
|
||||||
|
|
||||||
|
if (serialFlow) {
|
||||||
|
options.c_cflag |= CRTSCTS; // hardware flow control
|
||||||
|
} else {
|
||||||
|
options.c_cflag &= ~(CRTSCTS); // no hardware flow control
|
||||||
|
}
|
||||||
|
|
||||||
|
// At 115k (87 us per char), reading 1 char at a time results in increased
|
||||||
|
// CPU usage, since we actually can keep up with getting a small number of
|
||||||
|
// characters per loop. At 921k (11 us per char), we get more characters
|
||||||
|
// each time through the loop, so there is less advantage to setting VMIN
|
||||||
|
// to more than 1.
|
||||||
|
//
|
||||||
|
// CPU Usage at
|
||||||
|
// VMIN 115k 921k
|
||||||
|
// 1 7.0% 1.8%
|
||||||
|
// 10 2.7% 1.6%
|
||||||
|
// 100 1.2% 1.2%
|
||||||
|
//
|
||||||
|
// The problem with asking for more than 1 character per read is that each
|
||||||
|
// message will usually not be received until some bytes in the following
|
||||||
|
// message are available. That is often not a problem, but there are
|
||||||
|
// sometimes gaps of several 10s of milliseconds in the telemetry stream,
|
||||||
|
// and it is preferable to process messages as soon as they are available.
|
||||||
|
if (serialBaud <= 115200) {
|
||||||
|
options.c_cc[VMIN] = 10;
|
||||||
|
} else {
|
||||||
|
options.c_cc[VMIN] = 1;
|
||||||
|
}
|
||||||
|
options.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
tcsetattr(fd, TCSANOW, &options); // Set the new options for the port "NOW"
|
||||||
|
|
||||||
|
la_log(LOG_INFO, "opened serial port %s", serialPortName.c_str());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Telem_Serial::handle_read()
|
||||||
|
{
|
||||||
|
ssize_t res = read(fd, _recv_buf, _recv_buflen);
|
||||||
|
if (res == -1) {
|
||||||
|
la_log(LOG_INFO, "Failed read: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Serial::configure(INIReader *config)
|
||||||
|
{
|
||||||
|
serialPortName = config->Get("solo", "telemDev", "/dev/ttymxc1");
|
||||||
|
serialBaud = config->GetInteger("solo", "telemBaud", 57600);
|
||||||
|
serialFlow = config->GetBoolean("solo", "telemFlow", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telem_Serial::do_writer_sends()
|
||||||
|
{
|
||||||
|
while (_send_buf_start != _send_buf_stop) { // FIXME: use file descriptors!
|
||||||
|
bool tail_first = false;
|
||||||
|
if (_send_buf_stop < _send_buf_start) {
|
||||||
|
tail_first = true;
|
||||||
|
}
|
||||||
|
uint32_t bytes_to_send =
|
||||||
|
tail_first ? (send_buf_size() - _send_buf_start) : (_send_buf_stop - _send_buf_start);
|
||||||
|
|
||||||
|
int32_t sent = ::write(fd, (const char *)&_send_buf[_send_buf_start], bytes_to_send);
|
||||||
|
if (sent < 0) {
|
||||||
|
// cry
|
||||||
|
break;
|
||||||
|
} else if (sent == 0) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
_send_buf_start += sent;
|
||||||
|
if (_send_buf_start == send_buf_size()) {
|
||||||
|
_send_buf_start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Telem_Serial::send_message(const mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
char sendbuf[1024]; // large enough...
|
||||||
|
|
||||||
|
uint16_t messageLen = mavlink_msg_to_send_buffer((uint8_t *)sendbuf, &msg);
|
||||||
|
if (send_buffer_space_free() < messageLen) {
|
||||||
|
// dropped_packets++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_send_buf_stop >= _send_buf_start) {
|
||||||
|
uint16_t to_copy = send_buf_size() - _send_buf_stop;
|
||||||
|
if (to_copy > messageLen) {
|
||||||
|
to_copy = messageLen;
|
||||||
|
}
|
||||||
|
memcpy(&_send_buf[_send_buf_stop], sendbuf, to_copy);
|
||||||
|
_send_buf_stop += to_copy;
|
||||||
|
if (_send_buf_stop >= send_buf_size()) {
|
||||||
|
_send_buf_stop = 0;
|
||||||
|
}
|
||||||
|
to_copy = messageLen - to_copy;
|
||||||
|
if (to_copy) {
|
||||||
|
memcpy(&_send_buf[_send_buf_stop], &sendbuf[messageLen - to_copy], to_copy);
|
||||||
|
_send_buf_stop += to_copy;
|
||||||
|
if (_send_buf_stop >= send_buf_size()) {
|
||||||
|
_send_buf_stop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memcpy(&_send_buf[_send_buf_stop], &sendbuf[0], messageLen);
|
||||||
|
_send_buf_stop += messageLen;
|
||||||
|
if (_send_buf_stop >= send_buf_size()) {
|
||||||
|
_send_buf_stop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
44
sololink/flightcode/dataflash_logger/telem_serial.h
Normal file
44
sololink/flightcode/dataflash_logger/telem_serial.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
#include "telem_client.h"
|
||||||
|
|
||||||
|
class Telem_Serial : public Telem_Client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Telem_Serial(uint8_t *recv_buf, uint32_t recv_buflen) : Telem_Client(recv_buf, recv_buflen)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t handle_read();
|
||||||
|
|
||||||
|
void configure(INIReader *config);
|
||||||
|
void init() override;
|
||||||
|
|
||||||
|
void pack_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err, uint8_t &nfds);
|
||||||
|
void handle_select_fds(fd_set &fds_read, fd_set &fds_write, fd_set &fds_err, uint8_t &nfds);
|
||||||
|
|
||||||
|
bool send_message(const mavlink_message_t &message);
|
||||||
|
|
||||||
|
void open_serial_port();
|
||||||
|
|
||||||
|
void do_writer_sends();
|
||||||
|
|
||||||
|
bool any_data_to_send()
|
||||||
|
{
|
||||||
|
return _send_buf_start != _send_buf_stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/* send buffer stuff: */
|
||||||
|
uint8_t _send_buf[65536]; /* way too big */
|
||||||
|
uint32_t send_buf_size() const
|
||||||
|
{
|
||||||
|
return 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string serialPortName;
|
||||||
|
uint32_t serialBaud;
|
||||||
|
bool serialFlow;
|
||||||
|
};
|
45
sololink/flightcode/dflog/Makefile
Normal file
45
sololink/flightcode/dflog/Makefile
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
VPATH = ../util
|
||||||
|
|
||||||
|
INCS = -I../util
|
||||||
|
|
||||||
|
CFLAGS += -Wall $(INCS)
|
||||||
|
CXXFLAGS += -Wall $(INCS)
|
||||||
|
|
||||||
|
LIBS += -lpthread
|
||||||
|
|
||||||
|
SRCS_CPP = dflog_downloader.cpp
|
||||||
|
SRCS_C += util.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_CPP:.cpp=.o) $(SRCS_C:.c=.o)
|
||||||
|
|
||||||
|
MAIN = dflog
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.cpp) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
$(RM) ../util/*.o
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
173
sololink/flightcode/dflog/dataFlashMAVLink-to-artoo.py
Executable file
173
sololink/flightcode/dflog/dataFlashMAVLink-to-artoo.py
Executable file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
actually_sync_logs_to_artoo = False
|
||||||
|
|
||||||
|
class RecentLogLinker:
|
||||||
|
'''RecentLogLinker manages links to the most recent logs. These links are used by the app to retrieve the most recent logs
|
||||||
|
'''
|
||||||
|
def __init__(self, directory,link_dir):
|
||||||
|
self.directory = directory #location of dataflashlogs
|
||||||
|
self.symlink_directory = link_dir #loction of links to the dataflashlogs
|
||||||
|
# if you reduce this number, you're going to have to ensure we
|
||||||
|
# remove all old stale RECENT links somehow!
|
||||||
|
self.num_recent_links_to_create = 10
|
||||||
|
|
||||||
|
# directly copied from LogPruner
|
||||||
|
def lognum(self, filename):
|
||||||
|
base = filename.partition('.')
|
||||||
|
return int(base[0])
|
||||||
|
|
||||||
|
# directly copied from LogPruner
|
||||||
|
def ordered_list_of_log_filepaths(self):
|
||||||
|
binfiles_regex = re.compile("[0-9]+.BIN")
|
||||||
|
files = [ f for f in os.listdir(self.directory) if (binfiles_regex.match(f) and os.path.isfile(os.path.join(self.directory, f))) ]
|
||||||
|
files.sort(key=self.lognum)
|
||||||
|
return [ os.path.join(self.directory, f) for f in files ]
|
||||||
|
|
||||||
|
def update_links(self):
|
||||||
|
filepaths = self.ordered_list_of_log_filepaths()
|
||||||
|
#print("Filepaths: " + str(filepaths))
|
||||||
|
for i in range(0, self.num_recent_links_to_create):
|
||||||
|
if len(filepaths) == 0:
|
||||||
|
break
|
||||||
|
source = filepaths.pop()
|
||||||
|
if i == 0:
|
||||||
|
link_name = "RECENT.BIN"
|
||||||
|
else:
|
||||||
|
link_name = "RECENT-{0}.BIN".format(i)
|
||||||
|
|
||||||
|
link_path = os.path.join(self.symlink_directory, link_name)
|
||||||
|
if os.path.exists(link_path):
|
||||||
|
existing_source = None
|
||||||
|
try:
|
||||||
|
existing_source = os.readlink(link_path)
|
||||||
|
except OSError as e:
|
||||||
|
os.unlink(link_path)
|
||||||
|
if existing_source is not None:
|
||||||
|
#print("Leaving link alone")
|
||||||
|
if existing_source == source:
|
||||||
|
continue
|
||||||
|
os.unlink(link_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.symlink(source, link_path)
|
||||||
|
except OSError as e:
|
||||||
|
print "Failed to link ({0} to {1}): {2}".format(source, link_path, e.strerror)
|
||||||
|
break
|
||||||
|
|
||||||
|
class LogPruner:
|
||||||
|
'''LogPruner removes log files (e.g. 45.BIN) from the supplied
|
||||||
|
directory until only the suppied number of logs remain. Files are
|
||||||
|
removed in ascending numerical order
|
||||||
|
'''
|
||||||
|
def __init__(self, directory, max_files_remaining):
|
||||||
|
self.directory = directory
|
||||||
|
self.max_files_remaining = max_files_remaining
|
||||||
|
|
||||||
|
# directly copied to RecentLogLinker
|
||||||
|
def lognum(self, filename):
|
||||||
|
base = filename.partition('.')
|
||||||
|
return int(base[0])
|
||||||
|
|
||||||
|
# directly copied to RecentLogLinker
|
||||||
|
def ordered_list_of_log_filepaths(self):
|
||||||
|
binfiles_regex = re.compile("[0-9]+.BIN")
|
||||||
|
files = [ f for f in os.listdir(self.directory) if (binfiles_regex.match(f) and os.path.isfile(os.path.join(self.directory, f))) ]
|
||||||
|
files.sort(key=self.lognum)
|
||||||
|
return [ os.path.join(self.directory, f) for f in files ]
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not os.path.exists(self.directory):
|
||||||
|
print("Directory does not exist: {0}".format(self.directory))
|
||||||
|
return
|
||||||
|
ordered_list = self.ordered_list_of_log_filepaths()
|
||||||
|
while len(ordered_list) > self.max_files_remaining:
|
||||||
|
to_remove = ordered_list.pop(0)
|
||||||
|
try:
|
||||||
|
os.unlink(to_remove)
|
||||||
|
except IOError as e:
|
||||||
|
print "Failed to remove ({0}): {1}".format(to_remove, e.strerror)
|
||||||
|
break
|
||||||
|
|
||||||
|
class LogSyncer:
|
||||||
|
'''
|
||||||
|
an instance of LogSyncer loops over rsync'ing dataflash logs from
|
||||||
|
the log data directory on solo onto Artoo
|
||||||
|
'''
|
||||||
|
def __init__(self, src, dest, ssh_id_path, rate=100):
|
||||||
|
self.rsyncSrc = src
|
||||||
|
self.rsyncDest = dest
|
||||||
|
self.ssh_id_path = ssh_id_path
|
||||||
|
self.rate = rate
|
||||||
|
self.rsyncPartialDest = '.'.join((self.rsyncDest,'part'))
|
||||||
|
if not os.path.exists(src):
|
||||||
|
os.makedirs(src)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# quick sanity checks here. --delete is a very, very big hammer
|
||||||
|
if len(self.rsyncSrc) < 10:
|
||||||
|
print("short rsync src")
|
||||||
|
sys.exit(1)
|
||||||
|
if len(self.rsyncDest) < 10:
|
||||||
|
print("short rsync dest")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
recentloglinker = RecentLogLinker(src)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
recentloglinker.update_links()
|
||||||
|
|
||||||
|
cmd = [ 'ionice', '-c', '3', 'nice',
|
||||||
|
"rsync", "-a", "--size-only", "--progress",
|
||||||
|
'-e', 'ssh -o StrictHostKeyChecking=no -i "%s"' % self.ssh_id_path,
|
||||||
|
"--partial",
|
||||||
|
"--delete",
|
||||||
|
'--bwlimit=%s' % (self.rate,),
|
||||||
|
self.rsyncSrc, self.rsyncDest]
|
||||||
|
# print "cmd: %s" % (' '.join(cmd))
|
||||||
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout,stderr = p.communicate()
|
||||||
|
rc = p.returncode
|
||||||
|
if rc != 0:
|
||||||
|
print "stderr: (%s)" % stderr
|
||||||
|
time.sleep(1);
|
||||||
|
continue
|
||||||
|
|
||||||
|
# print "stdout: (%s)" % stdout
|
||||||
|
|
||||||
|
timeout = 10
|
||||||
|
if string.find(stdout,"xfr") != -1:
|
||||||
|
timeout = 1
|
||||||
|
time.sleep(timeout);
|
||||||
|
|
||||||
|
# TODO: get artoo's address from config
|
||||||
|
# TODO: get source and dest addresses from config
|
||||||
|
src = "/log/dataflash/"
|
||||||
|
dest = "root@10.1.1.1:/log/solo/dataflash"
|
||||||
|
link_dir = "/log/" #location of symlinks for app to retrieve
|
||||||
|
ssh_id_path = "/home/root/.ssh/id_rsa-mav-df-xfer"
|
||||||
|
max_log_files=50
|
||||||
|
|
||||||
|
p = LogPruner(src, max_log_files)
|
||||||
|
p.run()
|
||||||
|
|
||||||
|
if actually_sync_logs_to_artoo:
|
||||||
|
l = LogSyncer(src, dest, ssh_id_path)
|
||||||
|
l.run()
|
||||||
|
else:
|
||||||
|
# we still update the links on SoloLink so the app can fetch them
|
||||||
|
recentloglinker = RecentLogLinker(src,link_dir)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
recentloglinker.update_links()
|
||||||
|
time.sleep(10);
|
||||||
|
|
||||||
|
# neither reached nor necessary if the LogSyncer runs
|
||||||
|
while True:
|
||||||
|
time.sleep(86400)
|
497
sololink/flightcode/dflog/dflog_downloader.cpp
Normal file
497
sololink/flightcode/dflog/dflog_downloader.cpp
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <list>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <poll.h>
|
||||||
|
#include "util.h"
|
||||||
|
#include "../mavlink/c_library/ardupilotmega/mavlink.h"
|
||||||
|
#include "../mavlink/c_library/common/mavlink.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Serial port name
|
||||||
|
***********************************************************************/
|
||||||
|
string serialPortName;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
File descriptors
|
||||||
|
***********************************************************************/
|
||||||
|
int serial_fd;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Threading variables
|
||||||
|
***********************************************************************/
|
||||||
|
pthread_mutex_t mutex_msg;
|
||||||
|
pthread_mutex_t mutex_msgtype;
|
||||||
|
mavlink_message_t latest_msg;
|
||||||
|
sem_t sem_msg;
|
||||||
|
int msg_type = 0;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Log file variables
|
||||||
|
***********************************************************************/
|
||||||
|
int log_size;
|
||||||
|
ofstream logfile;
|
||||||
|
int log_num = 0;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
User input variables
|
||||||
|
***********************************************************************/
|
||||||
|
bool get_latest = false;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Serial tx/rx buffer size
|
||||||
|
***********************************************************************/
|
||||||
|
#define BUFSIZE 256
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Pointer to log buffer. Allocate a really big buffer later
|
||||||
|
that is the size of the log file. This is probably not the best way
|
||||||
|
to go about this, but it works in a pinch.
|
||||||
|
***********************************************************************/
|
||||||
|
char *logbuf;
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int serial_setup(int baud)
|
||||||
|
|
||||||
|
Description: The serial port initialization function. This function
|
||||||
|
initializes the serial port over which DSM data is sent to
|
||||||
|
the pixhawk. A return of 0 indicates an error.
|
||||||
|
***********************************************************************/
|
||||||
|
int serial_setup(int baud)
|
||||||
|
{
|
||||||
|
struct termios options;
|
||||||
|
|
||||||
|
serial_fd = open(serialPortName.c_str(), O_RDWR | O_NOCTTY);
|
||||||
|
|
||||||
|
if (serial_fd < 0) {
|
||||||
|
cerr << "Unable to open serial port " << serialPortName.c_str() << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcflush(serial_fd, TCIOFLUSH);
|
||||||
|
|
||||||
|
// Configure port for 8N1 transmission
|
||||||
|
tcgetattr(serial_fd, &options); // Gets the current options for the port
|
||||||
|
// Set the output baud rate
|
||||||
|
switch (baud) {
|
||||||
|
case 57600:
|
||||||
|
cfsetspeed(&options, B57600);
|
||||||
|
break;
|
||||||
|
case 115200:
|
||||||
|
cfsetspeed(&options, B115200);
|
||||||
|
break;
|
||||||
|
case 1500000:
|
||||||
|
cfsetspeed(&options, B1500000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cerr << "Unsupported baud rate" << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
options.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
|
||||||
|
options.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OPOST);
|
||||||
|
options.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
|
||||||
|
options.c_cflag &= ~(CSIZE | PARENB);
|
||||||
|
options.c_cflag |= CS8;
|
||||||
|
options.c_cflag |= CRTSCTS;
|
||||||
|
options.c_cc[VMIN] = 17;
|
||||||
|
options.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
tcsetattr(serial_fd, TCSANOW, &options); // Set the new options for the port "NOW"
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
tcflush(serial_fd, TCIOFLUSH);
|
||||||
|
|
||||||
|
cout << "Opened serial port " << serialPortName.c_str() << endl;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int print_pct()
|
||||||
|
|
||||||
|
Description: Prints a pct bar on the console and displays the
|
||||||
|
current download speed.
|
||||||
|
***********************************************************************/
|
||||||
|
void print_pct(float pct, float speed)
|
||||||
|
{
|
||||||
|
int maxWidth = 50;
|
||||||
|
int pos;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
cout << "[";
|
||||||
|
pos = maxWidth * pct;
|
||||||
|
for (i = 0; i < maxWidth; ++i) {
|
||||||
|
if (i < pos)
|
||||||
|
cout << "=";
|
||||||
|
else if (i == pos)
|
||||||
|
cout << ">";
|
||||||
|
else
|
||||||
|
cout << " ";
|
||||||
|
}
|
||||||
|
cout << "] " << int(pct * 100.0) << "%";
|
||||||
|
cout << " " << speed << " kB/s \r";
|
||||||
|
cout << flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int handle_log_data()
|
||||||
|
|
||||||
|
Description: Take log data and dump it into a log file
|
||||||
|
***********************************************************************/
|
||||||
|
int handle_log_data(mavlink_message_t *msg)
|
||||||
|
{
|
||||||
|
int ofs, cnt, id;
|
||||||
|
uint64_t now_us;
|
||||||
|
float speed = 0.0;
|
||||||
|
char logdat[128];
|
||||||
|
|
||||||
|
static int fileofs = 0;
|
||||||
|
static int disc_cnt = 0;
|
||||||
|
static uint64_t last_us = 0;
|
||||||
|
static int bytes_received = 0;
|
||||||
|
static int last_bytes_received = 0;
|
||||||
|
static char *buf_ptr = logbuf;
|
||||||
|
|
||||||
|
id = mavlink_msg_log_data_get_id(msg);
|
||||||
|
|
||||||
|
if (id != log_num) {
|
||||||
|
cout << "Got data from a different log, ignoring" << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ofs = mavlink_msg_log_data_get_ofs(msg);
|
||||||
|
cnt = mavlink_msg_log_data_get_count(msg);
|
||||||
|
mavlink_msg_log_data_get_data(msg, (uint8_t *)logdat);
|
||||||
|
|
||||||
|
// I don't think this ever happens, but if we get a zero
|
||||||
|
// count then we're done
|
||||||
|
if (cnt == 0) {
|
||||||
|
cout << endl;
|
||||||
|
cout << "Log download complete" << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data directly to the (really big) buffer
|
||||||
|
memcpy(buf_ptr, logdat, cnt);
|
||||||
|
buf_ptr += cnt;
|
||||||
|
|
||||||
|
if (fileofs != ofs)
|
||||||
|
disc_cnt++;
|
||||||
|
|
||||||
|
now_us = clock_gettime_us(CLOCK_MONOTONIC);
|
||||||
|
|
||||||
|
bytes_received += cnt;
|
||||||
|
|
||||||
|
// Output
|
||||||
|
if ((now_us - last_us) > 1e6) {
|
||||||
|
speed =
|
||||||
|
float(bytes_received - last_bytes_received) / 1024. / float((now_us - last_us) / 1e6);
|
||||||
|
|
||||||
|
print_pct(float(bytes_received) / float(log_size), speed);
|
||||||
|
|
||||||
|
last_us = now_us;
|
||||||
|
last_bytes_received = bytes_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the file position
|
||||||
|
fileofs = ofs + cnt;
|
||||||
|
|
||||||
|
// Are we done yet?
|
||||||
|
if (fileofs == log_size) {
|
||||||
|
print_pct(1, speed);
|
||||||
|
cout << endl;
|
||||||
|
cout << "Log download complete." << endl;
|
||||||
|
cout << "Downloaded " << float(bytes_received) / 1024. << "kB, dropped " << disc_cnt
|
||||||
|
<< " packet(s)" << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int mavlink_task()
|
||||||
|
|
||||||
|
Description: Thread task to pull mavlink data from the serial port
|
||||||
|
and dump it into a mutex-protected mavlink message.
|
||||||
|
|
||||||
|
When log data begins streaming, dumps log data into the
|
||||||
|
log file on the filesystem.
|
||||||
|
***********************************************************************/
|
||||||
|
void *mavlink_task(void *)
|
||||||
|
{
|
||||||
|
mavlink_message_t _msg;
|
||||||
|
mavlink_status_t mavlink_status;
|
||||||
|
int read_len;
|
||||||
|
int i;
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
int msgtolookfor;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
read_len = read(serial_fd, buf, BUFSIZE);
|
||||||
|
|
||||||
|
if (read_len < 0) {
|
||||||
|
if (errno != EAGAIN)
|
||||||
|
cerr << "Read err: " << errno << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_len > 0) {
|
||||||
|
for (i = 0; i < read_len; ++i) {
|
||||||
|
if (mavlink_parse_char(MAVLINK_COMM_0, buf[i], &_msg, &mavlink_status)) {
|
||||||
|
// If this is the type of message we're waiting for, put it on
|
||||||
|
// the latest_msg or handle its log data.
|
||||||
|
pthread_mutex_lock(&mutex_msgtype);
|
||||||
|
msgtolookfor = msg_type;
|
||||||
|
pthread_mutex_unlock(&mutex_msgtype);
|
||||||
|
|
||||||
|
if (_msg.msgid == msgtolookfor) {
|
||||||
|
if (_msg.msgid == MAVLINK_MSG_ID_LOG_DATA) {
|
||||||
|
if (handle_log_data(&_msg)) {
|
||||||
|
cout << "Writing to logfile...";
|
||||||
|
logfile.write(logbuf, log_size);
|
||||||
|
system("sync");
|
||||||
|
cout << "Done!" << endl;
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pthread_mutex_lock(&mutex_msg);
|
||||||
|
memcpy(&latest_msg, &_msg, sizeof(mavlink_message_t));
|
||||||
|
pthread_mutex_unlock(&mutex_msg);
|
||||||
|
sem_post(&sem_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: int wait_for_message()
|
||||||
|
|
||||||
|
Description: Blocks until a particular mavlink message type is received.
|
||||||
|
A return value of 0 indicates succes, -1 for a timeout.
|
||||||
|
Setting timeout_s to 0 blocks indefinitely.
|
||||||
|
***********************************************************************/
|
||||||
|
int wait_for_message(int _msg_type, mavlink_message_t *msg, int timeout_s)
|
||||||
|
{
|
||||||
|
struct timespec timeout;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// Tell the mavlink thread which message to look for
|
||||||
|
pthread_mutex_lock(&mutex_msgtype);
|
||||||
|
msg_type = _msg_type;
|
||||||
|
pthread_mutex_unlock(&mutex_msgtype);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_REALTIME, &timeout);
|
||||||
|
timeout.tv_sec += timeout_s;
|
||||||
|
|
||||||
|
if (timeout_s == 0)
|
||||||
|
sem_wait(&sem_msg);
|
||||||
|
else {
|
||||||
|
ret = sem_timedwait(&sem_msg, &timeout);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == ETIMEDOUT)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&mutex_msg);
|
||||||
|
memcpy(msg, &latest_msg, sizeof(mavlink_message_t));
|
||||||
|
pthread_mutex_unlock(&mutex_msg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: void request_log()
|
||||||
|
|
||||||
|
Description: Request a particular log file from the pixhawk.
|
||||||
|
***********************************************************************/
|
||||||
|
void request_log(void)
|
||||||
|
{
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
mavlink_message_t msg;
|
||||||
|
int msg_len;
|
||||||
|
char logfilename[] = "/log/log12345.bin";
|
||||||
|
|
||||||
|
// Wait for a heartbeat
|
||||||
|
if (wait_for_message(MAVLINK_MSG_ID_HEARTBEAT, &msg, 5) < 0) {
|
||||||
|
cout << "No heartbeat received" << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest log number (num_logs works, last_log_num doesn't)
|
||||||
|
if (get_latest) {
|
||||||
|
mavlink_msg_log_request_list_pack(1, 1, &msg, 1, 1, log_num, log_num);
|
||||||
|
msg_len = mavlink_msg_to_send_buffer((uint8_t *)buf, &msg);
|
||||||
|
|
||||||
|
if (write(serial_fd, buf, msg_len) != msg_len) {
|
||||||
|
cerr << "Serial port write error." << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Requesting latest log number...";
|
||||||
|
|
||||||
|
if (wait_for_message(MAVLINK_MSG_ID_LOG_ENTRY, &msg, 10) < 0) {
|
||||||
|
cout << "Unable to get log information" << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_num = mavlink_msg_log_entry_get_num_logs(&msg);
|
||||||
|
cout << "Latest log is " << log_num << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get some size information about the log we want.
|
||||||
|
mavlink_msg_log_request_list_pack(1, 1, &msg, 1, 1, log_num, log_num);
|
||||||
|
msg_len = mavlink_msg_to_send_buffer((uint8_t *)buf, &msg);
|
||||||
|
if (write(serial_fd, buf, msg_len) != msg_len) {
|
||||||
|
cerr << "Serial port write error." << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Waiting for log information..." << endl;
|
||||||
|
|
||||||
|
if (wait_for_message(MAVLINK_MSG_ID_LOG_ENTRY, &msg, 10) < 0) {
|
||||||
|
cout << "Unable to get log information" << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_size = mavlink_msg_log_entry_get_size(&msg);
|
||||||
|
if (log_size == 0) {
|
||||||
|
cout << "Log " << log_num << " does not exist." << endl;
|
||||||
|
exit(1);
|
||||||
|
} else {
|
||||||
|
cout << "Log size is " << float(log_size) / 1024. << "kB" << endl;
|
||||||
|
|
||||||
|
logbuf = new char[log_size];
|
||||||
|
if (logbuf == NULL) {
|
||||||
|
cout << "Unable to allocate buffer for log" << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the log file storage
|
||||||
|
sprintf(logfilename, "/log/log%i.bin", log_num);
|
||||||
|
logfile.open(logfilename, ios::out | ios::binary);
|
||||||
|
|
||||||
|
// Request log data
|
||||||
|
cout << "Beginning log download..." << endl;
|
||||||
|
mavlink_msg_log_request_data_pack(1, 1, &msg, 1, 1, log_num, 0, 0xFFFFFFFF);
|
||||||
|
msg_len = mavlink_msg_to_send_buffer((uint8_t *)buf, &msg);
|
||||||
|
|
||||||
|
// Let the thread know we've requested data.
|
||||||
|
pthread_mutex_lock(&mutex_msgtype);
|
||||||
|
msg_type = MAVLINK_MSG_ID_LOG_DATA;
|
||||||
|
pthread_mutex_unlock(&mutex_msgtype);
|
||||||
|
|
||||||
|
if (write(serial_fd, buf, msg_len) != msg_len) {
|
||||||
|
cerr << "Serial port write error." << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the mavlink thread do its thing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: int main(void)
|
||||||
|
|
||||||
|
Description: The main function. Initializes and runs the serial and
|
||||||
|
UDP threads.
|
||||||
|
***********************************************************************/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int baudrate;
|
||||||
|
pthread_t mavlink_ctx;
|
||||||
|
char usbdev_string[256] = "usb-3D_Robotics_PX4_FMU";
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *direntry;
|
||||||
|
bool foundDevice = false;
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
cout << "Usage: dflog <lognum|latest>" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(argv[1], "latest") == 0) {
|
||||||
|
cout << "Pulling latest log" << endl;
|
||||||
|
get_latest = true;
|
||||||
|
} else {
|
||||||
|
log_num = atoi(argv[1]);
|
||||||
|
cout << "Pulling log " << log_num << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cout cleanup
|
||||||
|
std::cout.precision(2);
|
||||||
|
std::cout.setf(ios::fixed, ios::floatfield);
|
||||||
|
|
||||||
|
dir = opendir("/dev/serial/by-id/");
|
||||||
|
if (dir == NULL) {
|
||||||
|
cerr << "open /dev/serial/by-id failed" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((direntry = readdir(dir))) {
|
||||||
|
if (!strncmp(direntry->d_name, usbdev_string, 23)) {
|
||||||
|
foundDevice = true;
|
||||||
|
strcpy(usbdev_string, direntry->d_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundDevice) {
|
||||||
|
cerr << "Unable to find USB device" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
// Serial port setup
|
||||||
|
serialPortName = "/dev/serial/by-id/";
|
||||||
|
serialPortName.append(usbdev_string);
|
||||||
|
baudrate = 115200;
|
||||||
|
if (!serial_setup(baudrate)) {
|
||||||
|
cerr << "Unable to initialize the serial send" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Threading initialization
|
||||||
|
pthread_mutex_init(&mutex_msg, NULL);
|
||||||
|
sem_init(&sem_msg, 0, 0);
|
||||||
|
|
||||||
|
// Start the mavlink rx thread
|
||||||
|
pthread_create(&mavlink_ctx, NULL, mavlink_task, NULL);
|
||||||
|
|
||||||
|
// Send a log request to the pixhawk
|
||||||
|
request_log();
|
||||||
|
|
||||||
|
// Wait for the mavlink thread to end
|
||||||
|
pthread_join(mavlink_ctx, NULL);
|
||||||
|
|
||||||
|
logfile.close();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
82
sololink/flightcode/dflog/loadLog.py
Executable file
82
sololink/flightcode/dflog/loadLog.py
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pymavlink import mavutil
|
||||||
|
import glob
|
||||||
|
import ConfigParser
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
SELECT_GPIO = "21"
|
||||||
|
ENABLE_GPIO = "19"
|
||||||
|
|
||||||
|
#GPIO direction set
|
||||||
|
def setGPIODir(gpio, direction):
|
||||||
|
dir_fd = open("/sys/class/gpio/gpio"+str(gpio)+"/direction", "w")
|
||||||
|
dir_fd.write(direction)
|
||||||
|
dir_fd.close()
|
||||||
|
|
||||||
|
#Open the GPIO
|
||||||
|
def openGPIO(gpio):
|
||||||
|
#Check and see if the GPIO is already exported
|
||||||
|
if not os.path.isdir("/sys/class/gpio/gpio"+str(gpio)):
|
||||||
|
#otherwise export it
|
||||||
|
exp_fd = open("/sys/class/gpio/export", "w")
|
||||||
|
exp_fd.write(gpio)
|
||||||
|
exp_fd.close()
|
||||||
|
|
||||||
|
setGPIODir(gpio, "out");
|
||||||
|
|
||||||
|
def closeGPIO(gpio):
|
||||||
|
unexp_fd = open("/sys/class/gpio/unexport", "w")
|
||||||
|
unexp_fd.write(gpio)
|
||||||
|
unexp_fd.close()
|
||||||
|
|
||||||
|
def setGPIO(gpio, value):
|
||||||
|
val_fd = open("/sys/class/gpio/gpio"+str(gpio)+"/value", "w")
|
||||||
|
val_fd.write(value)
|
||||||
|
val_fd.close()
|
||||||
|
|
||||||
|
def openSetClose(gpio, value):
|
||||||
|
openGPIO(gpio)
|
||||||
|
setGPIO(gpio, value)
|
||||||
|
closeGPIO(gpio)
|
||||||
|
|
||||||
|
#Set the GPIO low
|
||||||
|
def disconnectAndExit():
|
||||||
|
openSetClose(SELECT_GPIO, "0")
|
||||||
|
openSetClose(ENABLE_GPIO, "1")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("lognum", help="Log number to download, or 'latest'")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
#Log downloading process
|
||||||
|
print "Pixhawk log loader"
|
||||||
|
|
||||||
|
#Set the USB select GPIOs
|
||||||
|
openSetClose(SELECT_GPIO, "1")
|
||||||
|
openSetClose(ENABLE_GPIO, "0")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print "Checking for pixhawk on USB"
|
||||||
|
|
||||||
|
usb_devs = glob.glob('/dev/serial/by-id/usb-3D*')
|
||||||
|
if not usb_devs:
|
||||||
|
print "No pixhawk found on USB. Exiting."
|
||||||
|
disconnectAndExit()
|
||||||
|
|
||||||
|
print "Pixhawk found on USB, requesting log."
|
||||||
|
pixhawk_usb = usb_devs[-1]
|
||||||
|
m = mavutil.mavlink_connection(pixhawk_usb)
|
||||||
|
|
||||||
|
#Call the log downloader app
|
||||||
|
ret = subprocess.call(["dflog", str(args.lognum)])
|
||||||
|
|
||||||
|
disconnectAndExit()
|
||||||
|
|
40
sololink/flightcode/hostapd_ctrl/Makefile
Normal file
40
sololink/flightcode/hostapd_ctrl/Makefile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# To cross compile:
|
||||||
|
#
|
||||||
|
# Set up as usual for bitbake:
|
||||||
|
# $ . setup-environment build
|
||||||
|
#
|
||||||
|
# In the build directory:
|
||||||
|
# $ bitbake meta-ide-support
|
||||||
|
# $ . tmp/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
|
||||||
|
#
|
||||||
|
# Now a make in this directory should work.
|
||||||
|
|
||||||
|
VPATH = ../util
|
||||||
|
|
||||||
|
INCS = -I../util
|
||||||
|
|
||||||
|
CFLAGS += -Wall $(INCS)
|
||||||
|
|
||||||
|
SRCS_C = main.c hostapd_ctrl.c util.c
|
||||||
|
|
||||||
|
OBJS = $(SRCS_C:.c=.o)
|
||||||
|
|
||||||
|
MAIN = hostapd_ctrl
|
||||||
|
|
||||||
|
all: $(MAIN)
|
||||||
|
|
||||||
|
$(MAIN): $(OBJS)
|
||||||
|
$(LINK.c) -o $(MAIN) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *~ $(MAIN)
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
48
sololink/flightcode/hostapd_ctrl/main.c
Normal file
48
sololink/flightcode/hostapd_ctrl/main.c
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "util.h"
|
||||||
|
#include "hostapd_ctrl.h"
|
||||||
|
|
||||||
|
/* Test program for hostapd_ctrl */
|
||||||
|
|
||||||
|
/* make this small (e.g. 2) to test the table-too-small case */
|
||||||
|
#define STATIONS_MAX 10
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
void *handle;
|
||||||
|
hostapd_station_info_t station_info[STATIONS_MAX];
|
||||||
|
int stations;
|
||||||
|
int i;
|
||||||
|
char mac_string[MAC_STRING_LEN];
|
||||||
|
|
||||||
|
handle = hostapd_ctrl_new("wlan0-ap");
|
||||||
|
if (handle == NULL) {
|
||||||
|
printf("ERROR creating control connection\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
stations = STATIONS_MAX;
|
||||||
|
if (hostapd_ctrl_get_stations(handle, station_info, &stations) != 0) {
|
||||||
|
printf("ERROR getting station info\n");
|
||||||
|
hostapd_ctrl_delete(handle);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%d stations:\n", stations);
|
||||||
|
|
||||||
|
if (stations > STATIONS_MAX)
|
||||||
|
/* there are more stations than would fit in the table we supplied */
|
||||||
|
stations = STATIONS_MAX;
|
||||||
|
|
||||||
|
for (i = 0; i < stations; i++)
|
||||||
|
printf("%s\n", mac_ntoa(station_info[i].mac, mac_string));
|
||||||
|
|
||||||
|
hostapd_ctrl_delete(handle);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
} /* main */
|
14
sololink/flightcode/ini/Makefile
Normal file
14
sololink/flightcode/ini/Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
all:
|
||||||
|
|
||||||
|
clean:
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
75
sololink/flightcode/ini/cpp/INIReader.cpp
Normal file
75
sololink/flightcode/ini/cpp/INIReader.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Read an INI file into easy-to-access name/value pairs.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include "../ini.h"
|
||||||
|
#include "INIReader.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
INIReader::INIReader(string filename)
|
||||||
|
{
|
||||||
|
_error = ini_parse(filename.c_str(), ValueHandler, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int INIReader::ParseError()
|
||||||
|
{
|
||||||
|
return _error;
|
||||||
|
}
|
||||||
|
|
||||||
|
string INIReader::Get(string section, string name, string default_value)
|
||||||
|
{
|
||||||
|
string key = MakeKey(section, name);
|
||||||
|
return _values.count(key) ? _values[key] : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
long INIReader::GetInteger(string section, string name, long default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
const char *value = valstr.c_str();
|
||||||
|
char *end;
|
||||||
|
// This parses "1234" (decimal) and also "0x4D2" (hex)
|
||||||
|
long n = strtol(value, &end, 0);
|
||||||
|
return end > value ? n : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double INIReader::GetReal(string section, string name, double default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
const char *value = valstr.c_str();
|
||||||
|
char *end;
|
||||||
|
double n = strtod(value, &end);
|
||||||
|
return end > value ? n : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INIReader::GetBoolean(string section, string name, bool default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
// Convert to lower case to make string comparisons case-insensitive
|
||||||
|
std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
|
||||||
|
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
|
||||||
|
return true;
|
||||||
|
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
string INIReader::MakeKey(string section, string name)
|
||||||
|
{
|
||||||
|
string key = section + "." + name;
|
||||||
|
// Convert to lower case to make section/name lookups case-insensitive
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int INIReader::ValueHandler(void *user, const char *section, const char *name, const char *value)
|
||||||
|
{
|
||||||
|
INIReader *reader = (INIReader *)user;
|
||||||
|
string key = MakeKey(section, name);
|
||||||
|
if (reader->_values[key].size() > 0)
|
||||||
|
reader->_values[key] += "\n";
|
||||||
|
reader->_values[key] += value;
|
||||||
|
return 1;
|
||||||
|
}
|
51
sololink/flightcode/ini/cpp/INIReader.h
Normal file
51
sololink/flightcode/ini/cpp/INIReader.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Read an INI file into easy-to-access name/value pairs.
|
||||||
|
|
||||||
|
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
|
||||||
|
// Go to the project home page for more info:
|
||||||
|
//
|
||||||
|
// http://code.google.com/p/inih/
|
||||||
|
|
||||||
|
#ifndef __INIREADER_H__
|
||||||
|
#define __INIREADER_H__
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
|
||||||
|
// for simplicity here rather than speed, but it should be pretty decent.)
|
||||||
|
class INIReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Construct INIReader and parse given filename. See ini.h for more info
|
||||||
|
// about the parsing.
|
||||||
|
INIReader(std::string filename);
|
||||||
|
|
||||||
|
// Return the result of ini_parse(), i.e., 0 on success, line number of
|
||||||
|
// first error on parse error, or -1 on file open error.
|
||||||
|
int ParseError();
|
||||||
|
|
||||||
|
// Get a string value from INI file, returning default_value if not found.
|
||||||
|
std::string Get(std::string section, std::string name, std::string default_value);
|
||||||
|
|
||||||
|
// Get an integer (long) value from INI file, returning default_value if
|
||||||
|
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
|
||||||
|
long GetInteger(std::string section, std::string name, long default_value);
|
||||||
|
|
||||||
|
// Get a real (floating point double) value from INI file, returning
|
||||||
|
// default_value if not found or not a valid floating point value
|
||||||
|
// according to strtod().
|
||||||
|
double GetReal(std::string section, std::string name, double default_value);
|
||||||
|
|
||||||
|
// Get a boolean value from INI file, returning default_value if not found or if
|
||||||
|
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
|
||||||
|
// and valid false values are "false", "no", "off", "0" (not case sensitive).
|
||||||
|
bool GetBoolean(std::string section, std::string name, bool default_value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _error;
|
||||||
|
std::map< std::string, std::string > _values;
|
||||||
|
static std::string MakeKey(std::string section, std::string name);
|
||||||
|
static int ValueHandler(void *user, const char *section, const char *name, const char *value);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __INIREADER_H__
|
14
sololink/flightcode/ini/cpp/Makefile
Normal file
14
sololink/flightcode/ini/cpp/Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
all:
|
||||||
|
|
||||||
|
clean:
|
||||||
|
|
||||||
|
BASE := ../../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
174
sololink/flightcode/ini/ini.c
Normal file
174
sololink/flightcode/ini/ini.c
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
http://code.google.com/p/inih/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_SECTION 50
|
||||||
|
#define MAX_NAME 50
|
||||||
|
|
||||||
|
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||||
|
static char *rstrip(char *s)
|
||||||
|
{
|
||||||
|
char *p = s + strlen(s);
|
||||||
|
while (p > s && isspace((unsigned char)(*--p)))
|
||||||
|
*p = '\0';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first non-whitespace char in given string. */
|
||||||
|
static char *lskip(const char *s)
|
||||||
|
{
|
||||||
|
while (*s && isspace((unsigned char)(*s)))
|
||||||
|
s++;
|
||||||
|
return (char *)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first char c or ';' comment in given string, or pointer to
|
||||||
|
null at end of string if neither found. ';' must be prefixed by a whitespace
|
||||||
|
character to register as a comment. */
|
||||||
|
static char *find_char_or_comment(const char *s, char c)
|
||||||
|
{
|
||||||
|
int was_whitespace = 0;
|
||||||
|
while (*s && *s != c && !(was_whitespace && *s == ';')) {
|
||||||
|
was_whitespace = isspace((unsigned char)(*s));
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return (char *)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||||
|
static char *strncpy0(char *dest, const char *src, size_t size)
|
||||||
|
{
|
||||||
|
strncpy(dest, src, size);
|
||||||
|
dest[size - 1] = '\0';
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse_file(FILE *file, int (*handler)(void *, const char *, const char *, const char *),
|
||||||
|
void *user)
|
||||||
|
{
|
||||||
|
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||||
|
#if INI_USE_STACK
|
||||||
|
char line[INI_MAX_LINE];
|
||||||
|
#else
|
||||||
|
char *line;
|
||||||
|
#endif
|
||||||
|
char section[MAX_SECTION] = "";
|
||||||
|
char prev_name[MAX_NAME] = "";
|
||||||
|
|
||||||
|
char *start;
|
||||||
|
char *end;
|
||||||
|
char *name;
|
||||||
|
char *value;
|
||||||
|
int lineno = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
line = (char *)malloc(INI_MAX_LINE);
|
||||||
|
if (!line) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Scan through file line by line */
|
||||||
|
while (fgets(line, INI_MAX_LINE, file) != NULL) {
|
||||||
|
lineno++;
|
||||||
|
|
||||||
|
start = line;
|
||||||
|
#if INI_ALLOW_BOM
|
||||||
|
if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB &&
|
||||||
|
(unsigned char)start[2] == 0xBF) {
|
||||||
|
start += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
start = lskip(rstrip(start));
|
||||||
|
|
||||||
|
if (*start == ';' || *start == '#') {
|
||||||
|
/* Per Python ConfigParser, allow '#' comments at start of line */
|
||||||
|
}
|
||||||
|
#if INI_ALLOW_MULTILINE
|
||||||
|
else if (*prev_name && *start && start > line) {
|
||||||
|
/* Non-black line with leading whitespace, treat as continuation
|
||||||
|
of previous name's value (as per Python ConfigParser). */
|
||||||
|
if (!handler(user, section, prev_name, start) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (*start == '[') {
|
||||||
|
/* A "[section]" line */
|
||||||
|
end = find_char_or_comment(start + 1, ']');
|
||||||
|
if (*end == ']') {
|
||||||
|
*end = '\0';
|
||||||
|
strncpy0(section, start + 1, sizeof(section));
|
||||||
|
*prev_name = '\0';
|
||||||
|
} else if (!error) {
|
||||||
|
/* No ']' found on section line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
} else if (*start && *start != ';') {
|
||||||
|
/* Not a comment, must be a name[=:]value pair */
|
||||||
|
end = find_char_or_comment(start, '=');
|
||||||
|
if (*end != '=') {
|
||||||
|
end = find_char_or_comment(start, ':');
|
||||||
|
}
|
||||||
|
if (*end == '=' || *end == ':') {
|
||||||
|
*end = '\0';
|
||||||
|
name = rstrip(start);
|
||||||
|
value = lskip(end + 1);
|
||||||
|
end = find_char_or_comment(value, '\0');
|
||||||
|
if (*end == ';')
|
||||||
|
*end = '\0';
|
||||||
|
rstrip(value);
|
||||||
|
|
||||||
|
/* Valid name[=:]value pair found, call handler */
|
||||||
|
strncpy0(prev_name, name, sizeof(prev_name));
|
||||||
|
if (!handler(user, section, name, value) && !error)
|
||||||
|
error = lineno;
|
||||||
|
} else if (!error) {
|
||||||
|
/* No '=' or ':' found on name[=:]value line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if INI_STOP_ON_FIRST_ERROR
|
||||||
|
if (error)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
free(line);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
int ini_parse(const char *filename,
|
||||||
|
int (*handler)(void *, const char *, const char *, const char *), void *user)
|
||||||
|
{
|
||||||
|
FILE *file;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
file = fopen(filename, "r");
|
||||||
|
if (!file)
|
||||||
|
return -1;
|
||||||
|
error = ini_parse_file(file, handler, user);
|
||||||
|
fclose(file);
|
||||||
|
return error;
|
||||||
|
}
|
75
sololink/flightcode/ini/ini.h
Normal file
75
sololink/flightcode/ini/ini.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
http://code.google.com/p/inih/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __INI_H__
|
||||||
|
#define __INI_H__
|
||||||
|
|
||||||
|
/* Make this header file easier to include in C++ code */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||||
|
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||||
|
is "" if name=value pair parsed before any section heading. name:value
|
||||||
|
pairs are also supported as a concession to Python's ConfigParser.
|
||||||
|
|
||||||
|
For each name=value pair parsed, call handler function with given user
|
||||||
|
pointer as well as section, name, and value (data only valid for duration
|
||||||
|
of handler call). Handler should return nonzero on success, zero on error.
|
||||||
|
|
||||||
|
Returns 0 on success, line number of first error on parse error (doesn't
|
||||||
|
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||||
|
error (only when INI_USE_STACK is zero).
|
||||||
|
*/
|
||||||
|
int ini_parse(const char *filename,
|
||||||
|
int (*handler)(void *user, const char *section, const char *name, const char *value),
|
||||||
|
void *user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||||
|
close the file when it's finished -- the caller must do that. */
|
||||||
|
int ini_parse_file(FILE *file, int (*handler)(void *user, const char *section, const char *name,
|
||||||
|
const char *value),
|
||||||
|
void *user);
|
||||||
|
|
||||||
|
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||||
|
ConfigParser. If allowed, ini_parse() will call the handler with the same
|
||||||
|
name for each subsequent line parsed. */
|
||||||
|
#ifndef INI_ALLOW_MULTILINE
|
||||||
|
#define INI_ALLOW_MULTILINE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||||
|
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||||
|
#ifndef INI_ALLOW_BOM
|
||||||
|
#define INI_ALLOW_BOM 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to use stack, zero to use heap (malloc/free). */
|
||||||
|
#ifndef INI_USE_STACK
|
||||||
|
#define INI_USE_STACK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Stop parsing on first error (default is to keep parsing). */
|
||||||
|
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||||
|
#define INI_STOP_ON_FIRST_ERROR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum line length for any line in INI file. */
|
||||||
|
#ifndef INI_MAX_LINE
|
||||||
|
#define INI_MAX_LINE 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __INI_H__ */
|
160
sololink/flightcode/log/Log.cpp
Normal file
160
sololink/flightcode/log/Log.cpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include "util.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: Log()
|
||||||
|
|
||||||
|
Description: The log constructor. Opens an fstream file based on
|
||||||
|
the filename string. Sets the maximum size of the log file
|
||||||
|
and the maximum number of log files to be written.
|
||||||
|
***********************************************************************/
|
||||||
|
Log::Log(string filename, long int maxFileSize, int maxLogFiles)
|
||||||
|
: _maxFileSize(maxFileSize), _maxLogFiles(maxLogFiles), _logFilename(filename)
|
||||||
|
{
|
||||||
|
log_fd.precision(2);
|
||||||
|
log_fd.setf(ios::fixed, ios::floatfield);
|
||||||
|
|
||||||
|
// Open the log file
|
||||||
|
log_fd.open(filename.c_str(), ios::out | std::ofstream::app);
|
||||||
|
if (!log_fd) {
|
||||||
|
cerr << "Could not open log file" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: Log()
|
||||||
|
|
||||||
|
Description: Overloaded log constructor with an option for creating a
|
||||||
|
new log when the log starts up
|
||||||
|
***********************************************************************/
|
||||||
|
Log::Log(string filename, long int maxFileSize, int maxLogFiles, bool newLogOnBoot)
|
||||||
|
: _maxFileSize(maxFileSize), _maxLogFiles(maxLogFiles), _logFilename(filename)
|
||||||
|
{
|
||||||
|
log_fd.precision(2);
|
||||||
|
log_fd.setf(ios::fixed, ios::floatfield);
|
||||||
|
bool fileExists = doesFileExist(filename);
|
||||||
|
|
||||||
|
// Open the log file
|
||||||
|
log_fd.open(filename.c_str(), ios::out | std::ofstream::app);
|
||||||
|
if (!log_fd) {
|
||||||
|
cerr << "Could not open log file" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLogOnBoot && fileExists)
|
||||||
|
forceRoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: getFilesize()
|
||||||
|
|
||||||
|
Description: Gets the filesize of a file named by filename
|
||||||
|
***********************************************************************/
|
||||||
|
long int Log::getFilesize(string filename)
|
||||||
|
{
|
||||||
|
struct stat filestatus;
|
||||||
|
|
||||||
|
stat(filename.c_str(), &filestatus);
|
||||||
|
|
||||||
|
return filestatus.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: doesFileExist()
|
||||||
|
|
||||||
|
Description: Returns a bool indicating if the file named filename
|
||||||
|
exists. filename must be an absolute path.
|
||||||
|
***********************************************************************/
|
||||||
|
bool Log::doesFileExist(string filename)
|
||||||
|
{
|
||||||
|
struct stat filestatus;
|
||||||
|
|
||||||
|
return (stat(filename.c_str(), &filestatus) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
Function: char* getTimeString(void)
|
||||||
|
|
||||||
|
Description: Gets the current time string.
|
||||||
|
***********************************************************************/
|
||||||
|
const char *Log::getTimeString(void)
|
||||||
|
{
|
||||||
|
static char buffer[80];
|
||||||
|
|
||||||
|
clock_gettime_str_r(CLOCK_REALTIME, buffer);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: checkSizeAndRoll()
|
||||||
|
|
||||||
|
Description: Checks to see if the log file has grown too big and should
|
||||||
|
be rolled over. Rolls it and all previous logfiles
|
||||||
|
if necessary. The calling application should call this
|
||||||
|
function periodically after writing to the logfile.
|
||||||
|
***********************************************************************/
|
||||||
|
void Log::checkSizeAndRoll(void)
|
||||||
|
{
|
||||||
|
stringstream filename, newfilename;
|
||||||
|
|
||||||
|
// Check the size of the file. If its too big, roll the logfiles.
|
||||||
|
if (getFilesize(_logFilename) > _maxFileSize) {
|
||||||
|
// Roll the log files up to the max-1
|
||||||
|
for (int i = 1; i < _maxLogFiles; ++i) {
|
||||||
|
filename.str(string());
|
||||||
|
filename << _logFilename.c_str() << "." << dec << (int)(_maxLogFiles - i);
|
||||||
|
|
||||||
|
// See if the file exists
|
||||||
|
if (doesFileExist(filename.str())) {
|
||||||
|
newfilename.str(string());
|
||||||
|
newfilename << _logFilename.c_str() << "." << dec << (int)(_maxLogFiles - i + 1);
|
||||||
|
|
||||||
|
rename(filename.str().c_str(), newfilename.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll the current file
|
||||||
|
log_fd.close();
|
||||||
|
rename(_logFilename.c_str(), filename.str().c_str());
|
||||||
|
log_fd.open(_logFilename.c_str(), ios::out | std::ofstream::app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Function: forceRoll()
|
||||||
|
|
||||||
|
Description: Forces a file roll
|
||||||
|
***********************************************************************/
|
||||||
|
void Log::forceRoll(void)
|
||||||
|
{
|
||||||
|
stringstream filename, newfilename;
|
||||||
|
|
||||||
|
// Roll the log files up to the max-1
|
||||||
|
for (int i = 1; i < _maxLogFiles; ++i) {
|
||||||
|
filename.str(string());
|
||||||
|
filename << _logFilename.c_str() << "." << dec << (int)(_maxLogFiles - i);
|
||||||
|
|
||||||
|
// See if the file exists
|
||||||
|
if (doesFileExist(filename.str())) {
|
||||||
|
newfilename.str(string());
|
||||||
|
newfilename << _logFilename.c_str() << "." << dec << (int)(_maxLogFiles - i + 1);
|
||||||
|
|
||||||
|
rename(filename.str().c_str(), newfilename.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll the current file
|
||||||
|
log_fd.close();
|
||||||
|
rename(_logFilename.c_str(), filename.str().c_str());
|
||||||
|
log_fd.open(_logFilename.c_str(), ios::out | std::ofstream::app);
|
||||||
|
}
|
41
sololink/flightcode/log/Log.h
Normal file
41
sololink/flightcode/log/Log.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef _LOG_H
|
||||||
|
#define _LOG_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
Class: Log
|
||||||
|
|
||||||
|
Description: A simple logging class. Opens a logfile specified by the
|
||||||
|
calling function. The log file can be rolled when its
|
||||||
|
size gets large enough, out to maxLogFiles files.
|
||||||
|
***********************************************************************/
|
||||||
|
class Log
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Log(string filename, long int maxFileSize, int maxLogFiles);
|
||||||
|
Log(string filename, long int maxFileSize, int maxLogFiles, bool newLogOnBoot);
|
||||||
|
void checkSizeAndRoll(void);
|
||||||
|
void forceRoll(void);
|
||||||
|
|
||||||
|
// Left as a public for use in logging macros
|
||||||
|
const char *getTimeString(void);
|
||||||
|
|
||||||
|
// Leave the file descriptor public so it can be written to
|
||||||
|
// by the calling application.
|
||||||
|
ofstream log_fd;
|
||||||
|
|
||||||
|
private:
|
||||||
|
long int _maxFileSize;
|
||||||
|
int _maxLogFiles;
|
||||||
|
string _logFilename;
|
||||||
|
|
||||||
|
bool doesFileExist(string filename);
|
||||||
|
long int getFilesize(string filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //_LOG_H
|
14
sololink/flightcode/log/Makefile
Normal file
14
sololink/flightcode/log/Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
all:
|
||||||
|
|
||||||
|
clean:
|
||||||
|
|
||||||
|
BASE := ../..
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py --apply
|
||||||
|
|
||||||
|
fmt-diff:
|
||||||
|
@python $(BASE)/tools/build/clang-format-run.py
|
||||||
|
|
||||||
|
.PHONY: all clean fmt fmt-diff
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,27 @@
|
|||||||
|
/** @file
|
||||||
|
* @brief MAVLink comm protocol built from ardupilotmega.xml
|
||||||
|
* @see http://mavlink.org
|
||||||
|
*/
|
||||||
|
#ifndef MAVLINK_H
|
||||||
|
#define MAVLINK_H
|
||||||
|
|
||||||
|
#ifndef MAVLINK_STX
|
||||||
|
#define MAVLINK_STX 254
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAVLINK_ENDIAN
|
||||||
|
#define MAVLINK_ENDIAN MAVLINK_LITTLE_ENDIAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAVLINK_ALIGNED_FIELDS
|
||||||
|
#define MAVLINK_ALIGNED_FIELDS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAVLINK_CRC_EXTRA
|
||||||
|
#define MAVLINK_CRC_EXTRA 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "version.h"
|
||||||
|
#include "ardupilotmega.h"
|
||||||
|
|
||||||
|
#endif // MAVLINK_H
|
@ -0,0 +1,353 @@
|
|||||||
|
// MESSAGE AHRS PACKING
|
||||||
|
|
||||||
|
#define MAVLINK_MSG_ID_AHRS 163
|
||||||
|
|
||||||
|
typedef struct __mavlink_ahrs_t
|
||||||
|
{
|
||||||
|
float omegaIx; ///< X gyro drift estimate rad/s
|
||||||
|
float omegaIy; ///< Y gyro drift estimate rad/s
|
||||||
|
float omegaIz; ///< Z gyro drift estimate rad/s
|
||||||
|
float accel_weight; ///< average accel_weight
|
||||||
|
float renorm_val; ///< average renormalisation value
|
||||||
|
float error_rp; ///< average error_roll_pitch value
|
||||||
|
float error_yaw; ///< average error_yaw value
|
||||||
|
} mavlink_ahrs_t;
|
||||||
|
|
||||||
|
#define MAVLINK_MSG_ID_AHRS_LEN 28
|
||||||
|
#define MAVLINK_MSG_ID_163_LEN 28
|
||||||
|
|
||||||
|
#define MAVLINK_MSG_ID_AHRS_CRC 127
|
||||||
|
#define MAVLINK_MSG_ID_163_CRC 127
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define MAVLINK_MESSAGE_INFO_AHRS { \
|
||||||
|
"AHRS", \
|
||||||
|
7, \
|
||||||
|
{ { "omegaIx", NULL, MAVLINK_TYPE_FLOAT, 0, 0, offsetof(mavlink_ahrs_t, omegaIx) }, \
|
||||||
|
{ "omegaIy", NULL, MAVLINK_TYPE_FLOAT, 0, 4, offsetof(mavlink_ahrs_t, omegaIy) }, \
|
||||||
|
{ "omegaIz", NULL, MAVLINK_TYPE_FLOAT, 0, 8, offsetof(mavlink_ahrs_t, omegaIz) }, \
|
||||||
|
{ "accel_weight", NULL, MAVLINK_TYPE_FLOAT, 0, 12, offsetof(mavlink_ahrs_t, accel_weight) }, \
|
||||||
|
{ "renorm_val", NULL, MAVLINK_TYPE_FLOAT, 0, 16, offsetof(mavlink_ahrs_t, renorm_val) }, \
|
||||||
|
{ "error_rp", NULL, MAVLINK_TYPE_FLOAT, 0, 20, offsetof(mavlink_ahrs_t, error_rp) }, \
|
||||||
|
{ "error_yaw", NULL, MAVLINK_TYPE_FLOAT, 0, 24, offsetof(mavlink_ahrs_t, error_yaw) }, \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pack a ahrs message
|
||||||
|
* @param system_id ID of this system
|
||||||
|
* @param component_id ID of this component (e.g. 200 for IMU)
|
||||||
|
* @param msg The MAVLink message to compress the data into
|
||||||
|
*
|
||||||
|
* @param omegaIx X gyro drift estimate rad/s
|
||||||
|
* @param omegaIy Y gyro drift estimate rad/s
|
||||||
|
* @param omegaIz Z gyro drift estimate rad/s
|
||||||
|
* @param accel_weight average accel_weight
|
||||||
|
* @param renorm_val average renormalisation value
|
||||||
|
* @param error_rp average error_roll_pitch value
|
||||||
|
* @param error_yaw average error_yaw value
|
||||||
|
* @return length of the message in bytes (excluding serial stream start sign)
|
||||||
|
*/
|
||||||
|
static inline uint16_t mavlink_msg_ahrs_pack(uint8_t system_id, uint8_t component_id, mavlink_message_t* msg,
|
||||||
|
float omegaIx, float omegaIy, float omegaIz, float accel_weight, float renorm_val, float error_rp, float error_yaw)
|
||||||
|
{
|
||||||
|
#if MAVLINK_NEED_BYTE_SWAP || !MAVLINK_ALIGNED_FIELDS
|
||||||
|
char buf[MAVLINK_MSG_ID_AHRS_LEN];
|
||||||
|
_mav_put_float(buf, 0, omegaIx);
|
||||||
|
_mav_put_float(buf, 4, omegaIy);
|
||||||
|
_mav_put_float(buf, 8, omegaIz);
|
||||||
|
_mav_put_float(buf, 12, accel_weight);
|
||||||
|
_mav_put_float(buf, 16, renorm_val);
|
||||||
|
_mav_put_float(buf, 20, error_rp);
|
||||||
|
_mav_put_float(buf, 24, error_yaw);
|
||||||
|
|
||||||
|
memcpy(_MAV_PAYLOAD_NON_CONST(msg), buf, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#else
|
||||||
|
mavlink_ahrs_t packet;
|
||||||
|
packet.omegaIx = omegaIx;
|
||||||
|
packet.omegaIy = omegaIy;
|
||||||
|
packet.omegaIz = omegaIz;
|
||||||
|
packet.accel_weight = accel_weight;
|
||||||
|
packet.renorm_val = renorm_val;
|
||||||
|
packet.error_rp = error_rp;
|
||||||
|
packet.error_yaw = error_yaw;
|
||||||
|
|
||||||
|
memcpy(_MAV_PAYLOAD_NON_CONST(msg), &packet, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
msg->msgid = MAVLINK_MSG_ID_AHRS;
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
return mavlink_finalize_message(msg, system_id, component_id, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
return mavlink_finalize_message(msg, system_id, component_id, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pack a ahrs message on a channel
|
||||||
|
* @param system_id ID of this system
|
||||||
|
* @param component_id ID of this component (e.g. 200 for IMU)
|
||||||
|
* @param chan The MAVLink channel this message will be sent over
|
||||||
|
* @param msg The MAVLink message to compress the data into
|
||||||
|
* @param omegaIx X gyro drift estimate rad/s
|
||||||
|
* @param omegaIy Y gyro drift estimate rad/s
|
||||||
|
* @param omegaIz Z gyro drift estimate rad/s
|
||||||
|
* @param accel_weight average accel_weight
|
||||||
|
* @param renorm_val average renormalisation value
|
||||||
|
* @param error_rp average error_roll_pitch value
|
||||||
|
* @param error_yaw average error_yaw value
|
||||||
|
* @return length of the message in bytes (excluding serial stream start sign)
|
||||||
|
*/
|
||||||
|
static inline uint16_t mavlink_msg_ahrs_pack_chan(uint8_t system_id, uint8_t component_id, uint8_t chan,
|
||||||
|
mavlink_message_t* msg,
|
||||||
|
float omegaIx,float omegaIy,float omegaIz,float accel_weight,float renorm_val,float error_rp,float error_yaw)
|
||||||
|
{
|
||||||
|
#if MAVLINK_NEED_BYTE_SWAP || !MAVLINK_ALIGNED_FIELDS
|
||||||
|
char buf[MAVLINK_MSG_ID_AHRS_LEN];
|
||||||
|
_mav_put_float(buf, 0, omegaIx);
|
||||||
|
_mav_put_float(buf, 4, omegaIy);
|
||||||
|
_mav_put_float(buf, 8, omegaIz);
|
||||||
|
_mav_put_float(buf, 12, accel_weight);
|
||||||
|
_mav_put_float(buf, 16, renorm_val);
|
||||||
|
_mav_put_float(buf, 20, error_rp);
|
||||||
|
_mav_put_float(buf, 24, error_yaw);
|
||||||
|
|
||||||
|
memcpy(_MAV_PAYLOAD_NON_CONST(msg), buf, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#else
|
||||||
|
mavlink_ahrs_t packet;
|
||||||
|
packet.omegaIx = omegaIx;
|
||||||
|
packet.omegaIy = omegaIy;
|
||||||
|
packet.omegaIz = omegaIz;
|
||||||
|
packet.accel_weight = accel_weight;
|
||||||
|
packet.renorm_val = renorm_val;
|
||||||
|
packet.error_rp = error_rp;
|
||||||
|
packet.error_yaw = error_yaw;
|
||||||
|
|
||||||
|
memcpy(_MAV_PAYLOAD_NON_CONST(msg), &packet, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
msg->msgid = MAVLINK_MSG_ID_AHRS;
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
return mavlink_finalize_message_chan(msg, system_id, component_id, chan, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
return mavlink_finalize_message_chan(msg, system_id, component_id, chan, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encode a ahrs struct
|
||||||
|
*
|
||||||
|
* @param system_id ID of this system
|
||||||
|
* @param component_id ID of this component (e.g. 200 for IMU)
|
||||||
|
* @param msg The MAVLink message to compress the data into
|
||||||
|
* @param ahrs C-struct to read the message contents from
|
||||||
|
*/
|
||||||
|
static inline uint16_t mavlink_msg_ahrs_encode(uint8_t system_id, uint8_t component_id, mavlink_message_t* msg, const mavlink_ahrs_t* ahrs)
|
||||||
|
{
|
||||||
|
return mavlink_msg_ahrs_pack(system_id, component_id, msg, ahrs->omegaIx, ahrs->omegaIy, ahrs->omegaIz, ahrs->accel_weight, ahrs->renorm_val, ahrs->error_rp, ahrs->error_yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encode a ahrs struct on a channel
|
||||||
|
*
|
||||||
|
* @param system_id ID of this system
|
||||||
|
* @param component_id ID of this component (e.g. 200 for IMU)
|
||||||
|
* @param chan The MAVLink channel this message will be sent over
|
||||||
|
* @param msg The MAVLink message to compress the data into
|
||||||
|
* @param ahrs C-struct to read the message contents from
|
||||||
|
*/
|
||||||
|
static inline uint16_t mavlink_msg_ahrs_encode_chan(uint8_t system_id, uint8_t component_id, uint8_t chan, mavlink_message_t* msg, const mavlink_ahrs_t* ahrs)
|
||||||
|
{
|
||||||
|
return mavlink_msg_ahrs_pack_chan(system_id, component_id, chan, msg, ahrs->omegaIx, ahrs->omegaIy, ahrs->omegaIz, ahrs->accel_weight, ahrs->renorm_val, ahrs->error_rp, ahrs->error_yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a ahrs message
|
||||||
|
* @param chan MAVLink channel to send the message
|
||||||
|
*
|
||||||
|
* @param omegaIx X gyro drift estimate rad/s
|
||||||
|
* @param omegaIy Y gyro drift estimate rad/s
|
||||||
|
* @param omegaIz Z gyro drift estimate rad/s
|
||||||
|
* @param accel_weight average accel_weight
|
||||||
|
* @param renorm_val average renormalisation value
|
||||||
|
* @param error_rp average error_roll_pitch value
|
||||||
|
* @param error_yaw average error_yaw value
|
||||||
|
*/
|
||||||
|
#ifdef MAVLINK_USE_CONVENIENCE_FUNCTIONS
|
||||||
|
|
||||||
|
static inline void mavlink_msg_ahrs_send(mavlink_channel_t chan, float omegaIx, float omegaIy, float omegaIz, float accel_weight, float renorm_val, float error_rp, float error_yaw)
|
||||||
|
{
|
||||||
|
#if MAVLINK_NEED_BYTE_SWAP || !MAVLINK_ALIGNED_FIELDS
|
||||||
|
char buf[MAVLINK_MSG_ID_AHRS_LEN];
|
||||||
|
_mav_put_float(buf, 0, omegaIx);
|
||||||
|
_mav_put_float(buf, 4, omegaIy);
|
||||||
|
_mav_put_float(buf, 8, omegaIz);
|
||||||
|
_mav_put_float(buf, 12, accel_weight);
|
||||||
|
_mav_put_float(buf, 16, renorm_val);
|
||||||
|
_mav_put_float(buf, 20, error_rp);
|
||||||
|
_mav_put_float(buf, 24, error_yaw);
|
||||||
|
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, buf, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, buf, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
mavlink_ahrs_t packet;
|
||||||
|
packet.omegaIx = omegaIx;
|
||||||
|
packet.omegaIy = omegaIy;
|
||||||
|
packet.omegaIz = omegaIz;
|
||||||
|
packet.accel_weight = accel_weight;
|
||||||
|
packet.renorm_val = renorm_val;
|
||||||
|
packet.error_rp = error_rp;
|
||||||
|
packet.error_yaw = error_yaw;
|
||||||
|
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, (const char *)&packet, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, (const char *)&packet, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MAVLINK_MSG_ID_AHRS_LEN <= MAVLINK_MAX_PAYLOAD_LEN
|
||||||
|
/*
|
||||||
|
This varient of _send() can be used to save stack space by re-using
|
||||||
|
memory from the receive buffer. The caller provides a
|
||||||
|
mavlink_message_t which is the size of a full mavlink message. This
|
||||||
|
is usually the receive buffer for the channel, and allows a reply to an
|
||||||
|
incoming message with minimum stack space usage.
|
||||||
|
*/
|
||||||
|
static inline void mavlink_msg_ahrs_send_buf(mavlink_message_t *msgbuf, mavlink_channel_t chan, float omegaIx, float omegaIy, float omegaIz, float accel_weight, float renorm_val, float error_rp, float error_yaw)
|
||||||
|
{
|
||||||
|
#if MAVLINK_NEED_BYTE_SWAP || !MAVLINK_ALIGNED_FIELDS
|
||||||
|
char *buf = (char *)msgbuf;
|
||||||
|
_mav_put_float(buf, 0, omegaIx);
|
||||||
|
_mav_put_float(buf, 4, omegaIy);
|
||||||
|
_mav_put_float(buf, 8, omegaIz);
|
||||||
|
_mav_put_float(buf, 12, accel_weight);
|
||||||
|
_mav_put_float(buf, 16, renorm_val);
|
||||||
|
_mav_put_float(buf, 20, error_rp);
|
||||||
|
_mav_put_float(buf, 24, error_yaw);
|
||||||
|
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, buf, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, buf, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
mavlink_ahrs_t *packet = (mavlink_ahrs_t *)msgbuf;
|
||||||
|
packet->omegaIx = omegaIx;
|
||||||
|
packet->omegaIy = omegaIy;
|
||||||
|
packet->omegaIz = omegaIz;
|
||||||
|
packet->accel_weight = accel_weight;
|
||||||
|
packet->renorm_val = renorm_val;
|
||||||
|
packet->error_rp = error_rp;
|
||||||
|
packet->error_yaw = error_yaw;
|
||||||
|
|
||||||
|
#if MAVLINK_CRC_EXTRA
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, (const char *)packet, MAVLINK_MSG_ID_AHRS_LEN, MAVLINK_MSG_ID_AHRS_CRC);
|
||||||
|
#else
|
||||||
|
_mav_finalize_message_chan_send(chan, MAVLINK_MSG_ID_AHRS, (const char *)packet, MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MESSAGE AHRS UNPACKING
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field omegaIx from ahrs message
|
||||||
|
*
|
||||||
|
* @return X gyro drift estimate rad/s
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_omegaIx(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field omegaIy from ahrs message
|
||||||
|
*
|
||||||
|
* @return Y gyro drift estimate rad/s
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_omegaIy(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field omegaIz from ahrs message
|
||||||
|
*
|
||||||
|
* @return Z gyro drift estimate rad/s
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_omegaIz(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field accel_weight from ahrs message
|
||||||
|
*
|
||||||
|
* @return average accel_weight
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_accel_weight(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field renorm_val from ahrs message
|
||||||
|
*
|
||||||
|
* @return average renormalisation value
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_renorm_val(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field error_rp from ahrs message
|
||||||
|
*
|
||||||
|
* @return average error_roll_pitch value
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_error_rp(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get field error_yaw from ahrs message
|
||||||
|
*
|
||||||
|
* @return average error_yaw value
|
||||||
|
*/
|
||||||
|
static inline float mavlink_msg_ahrs_get_error_yaw(const mavlink_message_t* msg)
|
||||||
|
{
|
||||||
|
return _MAV_RETURN_float(msg, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decode a ahrs message into a struct
|
||||||
|
*
|
||||||
|
* @param msg The message to decode
|
||||||
|
* @param ahrs C-struct to decode the message contents into
|
||||||
|
*/
|
||||||
|
static inline void mavlink_msg_ahrs_decode(const mavlink_message_t* msg, mavlink_ahrs_t* ahrs)
|
||||||
|
{
|
||||||
|
#if MAVLINK_NEED_BYTE_SWAP
|
||||||
|
ahrs->omegaIx = mavlink_msg_ahrs_get_omegaIx(msg);
|
||||||
|
ahrs->omegaIy = mavlink_msg_ahrs_get_omegaIy(msg);
|
||||||
|
ahrs->omegaIz = mavlink_msg_ahrs_get_omegaIz(msg);
|
||||||
|
ahrs->accel_weight = mavlink_msg_ahrs_get_accel_weight(msg);
|
||||||
|
ahrs->renorm_val = mavlink_msg_ahrs_get_renorm_val(msg);
|
||||||
|
ahrs->error_rp = mavlink_msg_ahrs_get_error_rp(msg);
|
||||||
|
ahrs->error_yaw = mavlink_msg_ahrs_get_error_yaw(msg);
|
||||||
|
#else
|
||||||
|
memcpy(ahrs, _MAV_PAYLOAD(msg), MAVLINK_MSG_ID_AHRS_LEN);
|
||||||
|
#endif
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user