~jl2/racebox-tools

2aac47702839b06a5cdd1ca278c346f60ecb67ae — Jeremiah LaRocco 1 year, 2 months ago 019f2f8
Logging to SQLite database

Needs to be more robust, but more or less working.
A database.lisp => database.lisp +229 -0
@@ 0,0 1,229 @@
;; database.lisp

;; Copyright (c) 2023 Jeremiah LaRocco <jeremiah_larocco@fastmail.com>

;; Permission to use, copy, modify, and/or distribute this software for any
;; purpose with or without fee is hereby granted, provided that the above
;; copyright notice and this permission notice appear in all copies.

;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
;; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
;; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
;; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
;; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
;; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
;; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

(in-package :racebox-tools)

(defun create-db (db)
  (loop :for sql-file :in (uiop:directory-files (asdf:system-relative-pathname :racebox-tools "sql/")
                                                "create_table*.sql")
        :for the-sql = (alexandria:read-file-into-string sql-file)
        :do
           (sqlite:execute-non-query db the-sql)))

(defgeneric insert-message (db msg)
  (:documentation "Add msg to db."))

(defmethod insert-message (db (msg racebox-mini-data-message))
  (with-slots (itow
               year
               month
               day

               hour
               minute
               second

               validity-flags
               time-accuraccy
               nanosecond

               fix-status
               fix-status-flags
               date-time-flags
               number-of-svs

               longitude
               latitude

               wgs-altitude
               msl-altitude

               horizontal-accuracy
               vertical-accuracy

               speed
               heading

               speed-accuracy
               heading-accuracy

               pdop

               lat-lon-flags

               battery-status

               g-force-x
               g-force-y
               g-force-z

               rotation-rate-x
               rotation-rate-y
               rotation-rate-z
               ) msg

    (sqlite:execute-non-query
     db
     "INSERT INTO raw_data (
     itow,

     year,
     month,
     day,

     hour,
     minute,
     second,

     validity_flags,
     time_accuraccy,
     nanosecond,

     fix_status,
     fix_status_flags,
     date_time_flags,
     number_of_svs,

     longitude,
     latitude,

     wgs_altitude,
     msl_altitude,

     horizontal_accuracy,
     vertical_accuracy,

     speed,
     heading,

     speed_accuracy,
     heading_accuracy,

     pdop,

     lat_lon_flags,

     battery_status,

     g_force_x,
     g_force_y,
     g_force_z,

     rotation_rate_x,
     rotation_rate_y,
     rotation_rate_z
) values
 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
     itow

     year
     month
     day

     hour
     minute
     second

     validity-flags
     time-accuraccy
     nanosecond

     fix-status
     fix-status-flags
     date-time-flags
     number-of-svs

     longitude
     latitude

     wgs-altitude
     msl-altitude

     horizontal-accuracy
     vertical-accuracy

     speed
     heading

     speed-accuracy
     heading-accuracy

     pdop

     lat-lon-flags

     battery-status

     g-force-x
     g-force-y
     g-force-z

     rotation-rate-x
     rotation-rate-y
     rotation-rate-z))
  (sqlite:last-insert-rowid db))

(defmethod insert-message (db (msg gps-message))
  (with-slots (timestamp
               longitude
               latitude
               msl-altitude
               wgs-altitude
               speed
               heading
               g-force
               rotation
               raw-id) msg
    (sqlite:execute-non-query
     db
     "INSERT INTO gps_message (
  timestamp,
  longitude,
  latitude,
  msl_altitude,
  wgs_altitude,
  speed,
  heading,
  g_force_x,
  g_force_y,
  g_force_z,
  rotation_x,
  rotation_y,
  rotation_z,
  raw_id
) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
"
     (local-time:format-timestring nil timestamp)
     longitude
     latitude
     msl-altitude
     wgs-altitude
     speed
     heading
     (vx g-force)
     (vy g-force)
     (vz g-force)
     (vx rotation)
     (vy rotation)
     (vz rotation)
     raw-id))

  (sqlite:last-insert-rowid db))

(defun get-database-filename ()
  "Return a database filename with a timestamp."
  (asdf:system-relative-pathname :racebox-tools
                                 (format nil "databases/racebox-~a.db" (local-time:now))))

A databases/.gitignore => databases/.gitignore +1 -0
@@ 0,0 1,1 @@
*.db

M dbus.lisp => dbus.lisp +1 -0
@@ 67,6 67,7 @@
                                        "6e400003-b5a3-f393-e0a9-e50e24dcca9e"))



;; (dbus:define-dbus-object racebox-service
;;   (:path "/org/bluez/hci0/dev_D2_D6_A3_84_35_29/service000b/char000e"))
;; (dbus:define-dbus-signal-handler (racebox-service (wat))

M main.lisp => main.lisp +21 -7
@@ 16,12 16,26 @@

(in-package :racebox-tools)



(defun main (args)
  (declare (ignorable args))
  (connect)
  (inspect (read-current-value))
  (disconnect)
  ;; TODO: What should main do?
  0)
  (let ((db-name (get-database-filename)))
            (format t "db-name: ~a~%" db-name)
    (sqlite:with-open-database (db db-name)
      (create-db db))
    (unwind-protect
         (handler-case
             (progn
               (connect)
               (loop
                 :do
                    (sqlite:with-open-database (db db-name)
                      (let* ((raw-value (read-current-value))
                             (gps-value (to-gps-message raw-value)))
                        (with-slots (raw-id) gps-value
                          (setf raw-id (insert-message db raw-value))
                          (insert-message db gps-value))))
                    (sleep (/ 1 25))))
           (error (err)
             (format t "Received error: ~a~%Quitting GPS logger.~%~%" err)))
      (disconnect))
    0))

M messages.lisp => messages.lisp +2 -1
@@ 214,7 214,8 @@
  (speed 0.0 :type real)
  (heading 0.0 :type real)
  (g-force (vec3 0.0 0.0 0.0) :type vec3)
  (rotation (vec3 0.0 0.0 0.0) :type vec3))
  (rotation (vec3 0.0 0.0 0.0) :type vec3)
  (raw-id 0 :type fixnum))


(defun to-gps-message (data-message)

M package.lisp => package.lisp +4 -0
@@ 92,6 92,10 @@
           #:gps-message-g-force
           #:gps-message-rotation

           #:create-db
           #:get-data-base-filename
           #:insert-message
           #:main
           ;; Not yet implemented...
           ;; #:read-csv-stream
           ;; #:read-csv-file

A racebox-recorder.service => racebox-recorder.service +23 -0
@@ 0,0 1,23 @@
[Unit]
Description=RaceBox Mini Recorder

After=bluetooth.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1

# TODO: What's the best practice on choosing a user?
User=racebox

ExecStart=/usr/bin/env sbcl --non-interactive --no-userinit \
     --eval "(let ((quicklisp-init \"$HOME/quicklisp/setup.lisp\")) \
               (when (probe-file quicklisp-init) \
                 (load quicklisp-init)))" \
     --eval "(ql:quickload :racebox-tools)" \
     --eval "(main nil)"

[Install]
WantedBy=multi-user.target
\ No newline at end of file

M racebox-tools.asd => racebox-tools.asd +3 -0
@@ 29,15 29,18 @@
               #:dbus
               #:local-time
               #:local-time-duration
               #:utm
               #:sqlite
               #:dbus-tools
               #:3d-vectors
               #:uiop
               )

  :components ((:file "package")
               (:file "dbus")
               (:file "messages")
               (:file "importers")
               (:file "database")
               (:file "main"))

  :in-order-to ((test-op (test-op racebox-tools.test))))

A sql/create_table_gps_msg.sql => sql/create_table_gps_msg.sql +19 -0
@@ 0,0 1,19 @@
create table if not exists gps_message(
  timestamp datetime,
  longitude real,
  latitude real,
  msl_altitude real,
  wgs_altitude real,
  speed real,
  heading real,
  g_force_x real,
  g_force_y real,
  g_force_z real,
  rotation_x real,
  rotation_y real,
  rotation_z real,
  raw_id integer not null,
  FOREIGN KEY (raw_id)
        REFERENCES raw_data (raw_msg_id)
             ON DELETE SET NULL
)

A sql/create_table_raw_data.sql => sql/create_table_raw_data.sql +50 -0
@@ 0,0 1,50 @@
create table if not exists raw_data(
     raw_msg_id integer primary key autoincrement,
     itow integer,

     year integer,
     month integer,
     day integer,

     hour integer,
     minute integer,
     second integer,

     validity_flags integer,
     time_accuraccy integer,
     nanosecond integer,

     fix_status integer,
     fix_status_flags integer,
     date_time_flags integer,
     number_of_svs integer,

     longitude integer,
     latitude integer,

     wgs_altitude integer,
     msl_altitude integer,

     horizontal_accuracy integer,
     vertical_accuracy integer,

     speed integer,
     heading integer,

     speed_accuracy integer,
     heading_accuracy integer,

     pdop integer,

     lat_lon_flags integer,

     battery_status integer,

     g_force_x integer,
     g_force_y integer,
     g_force_z integer,

     rotation_rate_x integer,
     rotation_rate_y integer,
     rotation_rate_z integer
)