CUB-228: Add battery_calibration_offset to cameras table #7

Closed
Otto wants to merge 1 commits from agent/hex/CUB-228-battery-calibration-offset into dev
6 changed files with 49 additions and 14 deletions
Showing only changes of commit 1704d8a833 - Show all commits
+2 -2
View File
@@ -132,10 +132,10 @@ func GetCameraDetail(database *db.DB) http.HandlerFunc {
// Get camera info // Get camera info
var c models.Camera var c models.Camera
err := database.QueryRowContext(r.Context(), ` err := database.QueryRowContext(r.Context(), `
SELECT camera_id, friendly_name, mac_address, created_at, updated_at SELECT camera_id, friendly_name, mac_address, battery_calibration_offset, created_at, updated_at
FROM cameras WHERE camera_id = ? FROM cameras WHERE camera_id = ?
`, cameraID).Scan( `, cameraID).Scan(
&c.CameraID, &c.FriendlyName, &c.MacAddress, &c.CameraID, &c.FriendlyName, &c.MacAddress, &c.BatteryCalibrationOffset,
&c.CreatedAt, &c.UpdatedAt, &c.CreatedAt, &c.UpdatedAt,
) )
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
+28
View File
@@ -14,6 +14,9 @@ import (
//go:embed migrations/001_create_tables.sql //go:embed migrations/001_create_tables.sql
var migration001 string var migration001 string
//go:embed migrations/002_add_camera_calibration.sql
var migration002 string
// DB wraps the sql.DB with connection-level settings. // DB wraps the sql.DB with connection-level settings.
type DB struct { type DB struct {
*sql.DB *sql.DB
@@ -62,6 +65,12 @@ func Open(path string) (*DB, error) {
return nil, err return nil, err
} }
log.Println("Migrations complete") log.Println("Migrations complete")
} else {
// Run incremental migrations on existing databases
if err := runIncrementalMigrations(db); err != nil {
db.Close()
return nil, err
}
} }
return &DB{db}, nil return &DB{db}, nil
@@ -83,6 +92,25 @@ func migrate(db *sql.DB, sql string) error {
return nil return nil
} }
// runIncrementalMigrations applies migrations that haven't been run yet on
// an existing database (one where the 001 schema already exists).
func runIncrementalMigrations(db *sql.DB) error {
// Migration 002: add battery_calibration_offset if it doesn't exist
var colCount int
err := db.QueryRow(`SELECT COUNT(*) FROM pragma_table_info('cameras') WHERE name = 'battery_calibration_offset'`).Scan(&colCount)
if err != nil {
return err
}
if colCount == 0 {
log.Println("Running migration 002: add battery_calibration_offset")
if err := migrate(db, migration002); err != nil {
return err
}
}
return nil
}
// splitSQL splits a SQL string on semicolons, respecting quoted strings. // splitSQL splits a SQL string on semicolons, respecting quoted strings.
func splitSQL(sql string) []string { func splitSQL(sql string) []string {
var stmts []string var stmts []string
+6 -5
View File
@@ -3,11 +3,12 @@
-- Cameras table: registry of all GoPro cameras -- Cameras table: registry of all GoPro cameras
CREATE TABLE IF NOT EXISTS cameras ( CREATE TABLE IF NOT EXISTS cameras (
camera_id TEXT PRIMARY KEY, camera_id TEXT PRIMARY KEY,
friendly_name TEXT NOT NULL, friendly_name TEXT NOT NULL,
mac_address TEXT UNIQUE, mac_address TEXT UNIQUE,
created_at DATETIME NOT NULL DEFAULT (datetime('now')), battery_calibration_offset REAL,
updated_at DATETIME NOT NULL DEFAULT (datetime('now')) created_at DATETIME NOT NULL DEFAULT (datetime('now')),
updated_at DATETIME NOT NULL DEFAULT (datetime('now'))
); );
CREATE INDEX IF NOT EXISTS idx_cameras_mac ON cameras(mac_address); CREATE INDEX IF NOT EXISTS idx_cameras_mac ON cameras(mac_address);
@@ -0,0 +1,5 @@
-- Migration 002: Add battery_calibration_offset to cameras table
-- This column stores a per-camera calibration value for converting raw battery
-- readings (e.g. GoPro Hero 3 byte at offset 57) into percentage values.
ALTER TABLE cameras ADD COLUMN battery_calibration_offset REAL;
+2 -2
View File
@@ -404,9 +404,9 @@ func extractCameraID(topic string) string {
func getCamera(db *db.DB, cameraID string) (models.Camera, error) { func getCamera(db *db.DB, cameraID string) (models.Camera, error) {
var cam models.Camera var cam models.Camera
err := db.QueryRow( err := db.QueryRow(
"SELECT camera_id, friendly_name, COALESCE(mac_address,''), created_at, updated_at FROM cameras WHERE camera_id = ?", "SELECT camera_id, friendly_name, COALESCE(mac_address,''), COALESCE(battery_calibration_offset, NULL), created_at, updated_at FROM cameras WHERE camera_id = ?",
cameraID, cameraID,
).Scan(&cam.CameraID, &cam.FriendlyName, &cam.MacAddress, &cam.CreatedAt, &cam.UpdatedAt) ).Scan(&cam.CameraID, &cam.FriendlyName, &cam.MacAddress, &cam.BatteryCalibrationOffset, &cam.CreatedAt, &cam.UpdatedAt)
return cam, err return cam, err
} }
+6 -5
View File
@@ -7,11 +7,12 @@ import (
// Camera represents a registered GoPro camera in the system. // Camera represents a registered GoPro camera in the system.
type Camera struct { type Camera struct {
CameraID string `json:"camera_id"` CameraID string `json:"camera_id"`
FriendlyName string `json:"friendly_name"` FriendlyName string `json:"friendly_name"`
MacAddress string `json:"mac_address,omitempty"` MacAddress string `json:"mac_address,omitempty"`
CreatedAt time.Time `json:"created_at"` BatteryCalibrationOffset *float64 `json:"battery_calibration_offset,omitempty"`
UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} }
// StatusLog records a single status poll from an ESP8266 node. // StatusLog records a single status poll from an ESP8266 node.