0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Project 25Advent Calendar 2019

Day 24

うちのGoogle Homeをサンタさんが来るのが待ち遠しい子供のようにしてみました

Posted at

Googleが毎年やっているSanta Trackerを使ってサンタさんが今どこでプレゼントを配っているかを逐次報告してくれるようにしてみました。

Santa TrackerのAPI

色々諦めたこと

  • 緯度経度と距離から近くにいるよとかしたかった
    • 近くの定義が難しかったのでやめました
  • 住んでいるエリアにサンタさんが来たら、santa claus is comin to townをSpotifyでながす
    • Google Assistant APIの実装が必要そうなので1日では無理だったのでやめました

コード

main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/ikasamah/homecast"
)

const (
	// 自分の住んでいる場所に一番近いエリア
	myID     = "tokyo"
	santaURL = "https://firebasestorage.googleapis.com/v0/b/santa-tracker-firebase.appspot.com/o/route%2Fsanta_en.json?alt=media&2019b"
)

var jst *time.Location

func init() {
	var err error
	jst, err = time.LoadLocation("Asia/Tokyo")
	if err != nil {
		jst = time.FixedZone("Asia/Tokyo", 9*60*60)
	}
}

func main() {
	tk, err := GetTracker(santaURL)
	if err != nil {
		panic(err)
	}

	fmt.Println("Start stalking")
	for {
		r := tk.Current(time.Now())
		// なんかも同じメッセージを喋り続けられるとうざいので、ステータスが変わるまでキャッシュする 
		// キャッシュされてない場合のみメッセージを話させる
		if !r.IsCache {
			if r.Destination.ID == myID {
				// サンタさん キタ━━━(゚∀゚)━━━!! 
				googlehome("Santa Claus is coming to town!! Santa Claus is coming to town!! Santa Claus is coming to town!!")
				// 自分の街についたら通知をやめる
				break
			} else {
				if r.Status == statusDeliver {
					// 今サンタさんがプレゼントを配っている場所を通知
					googlehome(fmt.Sprintf("Santa Claus is in %s, %s\n", r.Destination.City, r.Destination.Region))
				}
			}
		}
		// ループを繰り返す感覚が短すぎるのでちょっとスリープ
		time.Sleep(5 * time.Second)
	}
	fmt.Println("finish")
}

// Google Homeデバイスに任意のメッセージ話させる
func googlehome(msg string) {
	fmt.Println(msg)
	ctx := context.Background()
	devices := homecast.LookupAndConnect(ctx)
	for _, device := range devices {
		device.Speak(ctx, msg, "en")
	}
}
tracker.go
package main

import (
	"encoding/json"
	"net/http"
	"time"
)

const (
	statusTakeOff = "Takeoff"
	statusMove    = "Move"
	statusDeliver = "Deliver"
	statusFinsish = "Finsish"
)

type APIResponse struct {
	Status       string        `json:"status"`
	Language     string        `json:"language"`
	TimeOffset   int           `json:"timeOffset"`
	Fingerprint  string        `json:"fingerprint"`
	Destinations []Destination `json:"destinations"`
}

type Destination struct {
	ID                string   `json:"id"`
	Arrival           int64    `json:"arrival"`
	Departure         int64    `json:"departure"`
	Population        int      `json:"population"`
	PresentsDelivered int      `json:"presentsDelivered"`
	City              string   `json:"city"`
	Region            string   `json:"region"`
	Location          Location `json:"location"`
}

type Location struct {
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
}

type Tracker struct {
	Destinations []Destination
	Cache        Cache
}

type Cache struct {
	ExpiredAt   int64
	Status      string
	Destination Destination
}

type Result struct {
	IsCache     bool
	Status      string
	Destination Destination
}

func GetTracker(uri string) (result *Tracker, err error) {
	resp, err := http.Get(uri)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	var r APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return
	}

	return &Tracker{
		Destinations: r.Destinations,
	}, nil
}

func (tk *Tracker) SetCache(dest Destination, status string, expiredAt int64) {
	tk.Cache = Cache{
		Destination: dest,
		Status:      status,
		ExpiredAt:   expiredAt,
	}
}

func (tk *Tracker) Current(tt time.Time) Result {
	ts := tt.Unix() * 1000

	c := tk.Cache
	if c.Status != "" && c.ExpiredAt >= ts {
		return Result{
			Status:      c.Status,
			Destination: c.Destination,
			IsCache:     true,
		}
	}

	home := tk.Destinations[0]
	if home.Departure > ts {
		return Result{
			Status:      statusTakeOff,
			Destination: home,
		}
	}

	landing := tk.Destinations[len(tk.Destinations)-1]
	if landing.Arrival < ts {
		return Result{
			Status:      statusFinsish,
			Destination: landing,
		}
	}

	ds := tk.Destinations[1:]
	for _, dest := range ds {
		if dest.Arrival > ts {
			tk.SetCache(dest, statusMove, dest.Arrival-1)
			return Result{
				Status:      statusMove,
				Destination: dest,
			}
		} else if dest.Arrival <= ts && dest.Departure >= ts {
			tk.SetCache(home, statusDeliver, dest.Departure)
			return Result{
				Status:      statusDeliver,
				Destination: dest,
			}
		}
	}
	return Result{
		Status:      statusFinsish,
		Destination: landing,
	}
}

テストをしている時点でサンタさんが日本に来てしまったので、やってみたい人は来年以降楽しんでください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?