From 57111e44f4798dad8619d262434597867cdb5ab0 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Thu, 26 Mar 2026 20:31:26 -0700 Subject: [PATCH] Switch Garmin messaging from IPC Inbound API to MapShare API --- garmin.go | 92 ++++++++++++++++++++++++++----------------------------- main.go | 38 +++++++++++------------ 2 files changed, 63 insertions(+), 67 deletions(-) diff --git a/garmin.go b/garmin.go index 35fcba3..4336c61 100644 --- a/garmin.go +++ b/garmin.go @@ -1,68 +1,61 @@ package main import ( - "bytes" "encoding/json" "fmt" "io" "log" "net/http" - "time" + "net/url" + "strings" ) type garminClient struct { - c *http.Client - apiKey string + c *http.Client + mapshareID string } -type garminMessageRequest struct { - Messages []garminMessage `json:"messages"` +type garminResponse struct { + Success bool `json:"success"` + Error *struct { + Code int `json:"code"` + Msg string `json:"msg"` + } `json:"error"` } -type garminMessage struct { - Recipients []string `json:"recipients"` - Sender string `json:"sender"` - Timestamp string `json:"timestamp"` - Message string `json:"message"` -} - -type garminMessageResponse struct { - Count int `json:"count"` -} - -func newGarminClient(apiKey string) *garminClient { +func newGarminClient(mapshareID string) *garminClient { return &garminClient{ - c: &http.Client{}, - apiKey: apiKey, + c: &http.Client{}, + mapshareID: mapshareID, } } -func (gc *garminClient) sendMessage(imeis []string, sender, msg string) error { - buf := &bytes.Buffer{} - err := json.NewEncoder(buf).Encode(garminMessageRequest{ - Messages: []garminMessage{ - { - Recipients: imeis, - Sender: sender, - Timestamp: time.Now().UTC().Format("2006-01-02T15:04:05Z"), - Message: msg, - }, - }, - }) +func (gc *garminClient) sendMessage(deviceIDs []string, sender, msg string) error { + for _, deviceID := range deviceIDs { + err := gc.sendToDevice(deviceID, sender, msg) + if err != nil { + return fmt.Errorf("device %s: %w", deviceID, err) + } + } + return nil +} +func (gc *garminClient) sendToDevice(deviceID, sender, msg string) error { + data := url.Values{} + data.Set("deviceIds", deviceID) + data.Set("messageText", msg) + data.Set("fromAddr", sender) + + endpoint := fmt.Sprintf("https://share.garmin.com/%s/Map/SendMessageToDevices", gc.mapshareID) + + log.Printf("[->garmin] %s %s", endpoint, data.Encode()) + + req, err := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode())) if err != nil { return err } - log.Printf("[->garmin] %s", buf.String()) - - req, err := http.NewRequest("POST", "https://ipcinbound.inreachapp.com/IPC/IPCInboundApi/api/Messaging/Message", buf) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("x-api-key", gc.apiKey) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := gc.c.Do(req) if err != nil { @@ -72,20 +65,23 @@ func (gc *garminClient) sendMessage(imeis []string, sender, msg string) error { defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) + log.Printf("[<-garmin] %d %s", resp.StatusCode, string(body)) + if resp.StatusCode != 200 { - return fmt.Errorf("%s", string(body)) + return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } - grResp := garminMessageResponse{} + var grResp garminResponse err = json.Unmarshal(body, &grResp) if err != nil { - return err + return fmt.Errorf("parse response: %w", err) } - log.Printf("[<-garmin] %s", string(body)) - - if grResp.Count != len(imeis) { - return fmt.Errorf("expected %d messages, got %d", len(imeis), grResp.Count) + if !grResp.Success { + if grResp.Error != nil { + return fmt.Errorf("garmin error %d: %s", grResp.Error.Code, grResp.Error.Msg) + } + return fmt.Errorf("garmin returned success=false") } return nil diff --git a/main.go b/main.go index 4be66e9..6e07a46 100644 --- a/main.go +++ b/main.go @@ -13,12 +13,12 @@ type PHandler struct { tmpl *template.Template pd *pdClient gc *garminClient - garminIMEIs []string - garminSender string + garminDeviceIDs []string + garminSender string mux *http.ServeMux } -func NewPHandler(pdRoutingKey, garminAPIKey string, garminIMEIs []string, garminSender string) (*PHandler, error) { +func NewPHandler(pdRoutingKey, garminMapshareID string, garminDeviceIDs []string, garminSender string) (*PHandler, error) { tmpl := template.New("index.html") tmpl.Funcs(template.FuncMap{ @@ -33,13 +33,13 @@ func NewPHandler(pdRoutingKey, garminAPIKey string, garminIMEIs []string, garmin ph := &PHandler{ tmpl: tmpl, pd: newPDClient(pdRoutingKey), - garminIMEIs: garminIMEIs, + garminDeviceIDs: garminDeviceIDs, garminSender: garminSender, mux: http.NewServeMux(), } - if garminAPIKey != "" { - ph.gc = newGarminClient(garminAPIKey) + if garminMapshareID != "" { + ph.gc = newGarminClient(garminMapshareID) } ph.mux.HandleFunc("/{$}", ph.serveRoot) @@ -86,7 +86,7 @@ func (ph *PHandler) sendAlert(m string) error { if ph.gc != nil { go func() { - err := ph.gc.sendMessage(ph.garminIMEIs, ph.garminSender, m) + err := ph.gc.sendMessage(ph.garminDeviceIDs, ph.garminSender, m) if err != nil { res <- fmt.Errorf("Error sending to Garmin: %w", err) } else { @@ -150,28 +150,28 @@ func main() { log.Fatalf("please set PD_ROUTING_KEY") } - garminAPIKey := os.Getenv("GARMIN_API_KEY") + garminMapshareID := os.Getenv("GARMIN_MAPSHARE_ID") - garminIMEIStr := os.Getenv("GARMIN_IMEI") - if garminAPIKey != "" && garminIMEIStr == "" { - log.Fatalf("please set GARMIN_IMEI") + garminDeviceIDStr := os.Getenv("GARMIN_DEVICE_IDS") + if garminMapshareID != "" && garminDeviceIDStr == "" { + log.Fatalf("please set GARMIN_DEVICE_IDS") } - var garminIMEIs []string - if garminIMEIStr != "" { - for _, imei := range strings.Split(garminIMEIStr, ",") { - imei = strings.TrimSpace(imei) - if imei != "" { - garminIMEIs = append(garminIMEIs, imei) + var garminDeviceIDs []string + if garminDeviceIDStr != "" { + for _, id := range strings.Split(garminDeviceIDStr, ",") { + id = strings.TrimSpace(id) + if id != "" { + garminDeviceIDs = append(garminDeviceIDs, id) } } } garminSender := os.Getenv("GARMIN_SENDER") - if garminAPIKey != "" && garminSender == "" { + if garminMapshareID != "" && garminSender == "" { log.Fatalf("please set GARMIN_SENDER") } - ph, err := NewPHandler(pdRoutingKey, garminAPIKey, garminIMEIs, garminSender) + ph, err := NewPHandler(pdRoutingKey, garminMapshareID, garminDeviceIDs, garminSender) if err != nil { log.Fatalf("NewPHandler: %s", err) }