├── eddystone └── main.go ├── ibeacon └── main.go ├── .travis.yml ├── README.md ├── ibeacon.go └── eddystone.go /eddystone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/kkdai/beacon" 5 | ) 6 | 7 | func main() { 8 | ed := NewEddystoneUIDBeacon("00010203040506070809", "aabbccddeeff", -20) 9 | ed.Advertise() 10 | } 11 | -------------------------------------------------------------------------------- /ibeacon/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/kkdai/beacon" 4 | 5 | func main() { 6 | ib := beacon.NewIBeacon("AA6062F098CA42118EC4193EB73CCEB6", "Gopher", -59) 7 | ib.SetiBeaconVersion(1, 2) 8 | ib.AddCountService() 9 | ib.AddBatteryService() 10 | ib.Advertise() 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.11" 5 | - tip 6 | 7 | before_install: 8 | - go get golang.org/x/lint/golint 9 | 10 | script: 11 | - go vet ./... 12 | # - $HOME/gopath/bin/goveralls -coverprofile=coverage.cov -service=travis-ci 13 | # - bash <(curl -s https://codecov.io/bash) 14 | #- go test -bench=. -benchmem ./... 15 | #- sh ./install_all_cmd.sh 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Beacon Simulator: A simple beacon simulator (iBeacon/Eddystone) in Golang 2 | ================== 3 | 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/kkdai/beacon/master/LICENSE) [![GoDoc](https://godoc.org/github.com/kkdai/beacon?status.svg)](https://godoc.org/github.com/kkdai/beacon) [![Build Status](https://travis-ci.org/kkdai/beacon.svg?branch=master)](https://travis-ci.org/kkdai/beacon) 5 | 6 | 7 | This package is summarized golang beacon simulator with paypal/gatt package. It supports major two beacon as follow: 8 | 9 | - iBeacon: Apple [iBeacon](https://developer.apple.com/ibeacon/) 10 | - Eddystone: Google [Eddystone](https://github.com/google/eddystone) 11 | 12 | Install library 13 | --------------- 14 | `go get github.com/kkdai/beacon` 15 | 16 | 17 | Install binary 18 | --------------- 19 | 20 | - Eddystone: `go install github.com/kkdai/beacon/eddystone` 21 | - iBeacon: `go install github.com/kkdai/beacon/ibeacon` 22 | 23 | Simulator iBeacon 24 | --------------- 25 | 26 | ```go 27 | 28 | package main 29 | 30 | import ( 31 | . "github.com/kkdai/beacon" 32 | ) 33 | 34 | func main() { 35 | ib := NewIBeacon("AA6062F098CA42118EC4193EB73CCEB6", "Gopher", -59) 36 | ib.SetiBeaconVersion(1, 2) 37 | ib.AddCountService() 38 | ib.AddBatteryService() 39 | ib.Advertise() 40 | } 41 | ``` 42 | 43 | 44 | Simulator Eddystone 45 | --------------- 46 | 47 | ```go 48 | 49 | package main 50 | 51 | import ( 52 | . "github.com/kkdai/beacon" 53 | ) 54 | 55 | func main() { 56 | ed := NewEddystoneURLBeacon("http://www.evanlin.com", -20) 57 | ed.Advertise() 58 | } 59 | ``` 60 | 61 | Inspired by 62 | --------------- 63 | 64 | - [github.com/go-ble/ble](github.com/go-ble/ble) 65 | - [https://github.com/suapapa/go_eddystone](https://github.com/suapapa/go_eddystone) 66 | 67 | Project52 68 | --------------- 69 | 70 | It is one of my [project 52](https://github.com/kkdai/project52). 71 | 72 | 73 | License 74 | --------------- 75 | 76 | This package is licensed under MIT license. See LICENSE for details. 77 | 78 | -------------------------------------------------------------------------------- /ibeacon.go: -------------------------------------------------------------------------------- 1 | package beacon 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/go-ble/ble" 10 | "github.com/go-ble/ble/examples/lib" 11 | "github.com/go-ble/ble/examples/lib/dev" 12 | ) 13 | 14 | type IBeacon struct { 15 | serviceList []*ble.Service 16 | BeaconDev ble.Device 17 | 18 | //Device Information 19 | DevUUID string 20 | DevName string 21 | DevMajorVersion uint16 22 | DevMinorVersion uint16 23 | PowerLevel int8 24 | } 25 | 26 | func NewIBeacon(devUUID string, name string, powerLevel int8) *IBeacon { 27 | d, err := dev.NewDevice("default") 28 | if err != nil { 29 | log.Fatalf("can't new device : %s", err) 30 | } 31 | 32 | return &IBeacon{ 33 | BeaconDev: d, 34 | DevUUID: devUUID, 35 | DevName: name, 36 | DevMajorVersion: 1, 37 | DevMinorVersion: 1, 38 | PowerLevel: powerLevel, 39 | } 40 | return &IBeacon{} 41 | } 42 | 43 | func (ib *IBeacon) SetiBeaconVersion(major, minor uint16) { 44 | ib.DevMajorVersion = major 45 | ib.DevMinorVersion = minor 46 | } 47 | 48 | func (ib *IBeacon) AddBatteryService() { 49 | sev := ble.NewService(ble.BatteryUUID) 50 | ib.serviceList = append(ib.serviceList, sev) 51 | } 52 | 53 | func (ib *IBeacon) AddCountService() { 54 | testSvc := ble.NewService(lib.TestSvcUUID) 55 | testSvc.AddCharacteristic(lib.NewCountChar()) 56 | testSvc.AddCharacteristic(lib.NewEchoChar()) 57 | ib.serviceList = append(ib.serviceList, testSvc) 58 | } 59 | 60 | func (ib *IBeacon) Advertise() error { 61 | ble.SetDefaultDevice(ib.BeaconDev) 62 | 63 | for _, service := range ib.serviceList { 64 | if err := ble.AddService(service); err != nil { 65 | log.Fatalf("can't add service: %s", err) 66 | } 67 | } 68 | 69 | // Advertise for specified durantion, or until interrupted by user. 70 | fmt.Printf("Advertising for %s...\n", 30*time.Second) 71 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), 30*time.Second)) 72 | return ble.AdvertiseNameAndServices(ctx, "Gopher") 73 | 74 | // // Register optional handlers. 75 | // d.Handle( 76 | // gatt.CentralConnected(func(c gatt.Central) { fmt.Println("Connect: ", c.ID()) }), 77 | // gatt.CentralDisconnected(func(c gatt.Central) { fmt.Println("Disconnect: ", c.ID()) }), 78 | // ) 79 | 80 | // // A mandatory handler for monitoring device state. 81 | // onStateChanged := func(d gatt.Device, s gatt.State) { 82 | // fmt.Printf("State: %s\n", s) 83 | // switch s { 84 | // case gatt.StatePoweredOn: 85 | // // Setup GAP and GATT services for Linux implementation. 86 | // // OS X doesn't export the access of these services. 87 | // d.AddService(service.NewGapService(ib.DevName)) // no effect on OS X 88 | // d.AddService(service.NewGattService()) // no effect on OS X 89 | 90 | // //Append services 91 | // serviceUUIDs := []gatt.UUID{} 92 | // for _, v := range ib.serviceList { 93 | // d.AddService(v) 94 | // serviceUUIDs = append(serviceUUIDs, v.UUID()) 95 | // } 96 | 97 | // // Advertise device name and service's UUIDs. 98 | // d.AdvertiseNameAndServices(ib.DevName, serviceUUIDs) 99 | 100 | // // Advertise as an OpenBeacon iBeacon 101 | // d.AdvertiseIBeacon(gatt.MustParseUUID(ib.DevUUID), ib.DevMajorVersion, ib.DevMinorVersion, ib.PowerLevel) 102 | 103 | // default: 104 | // } 105 | // } 106 | 107 | // d.Init(onStateChanged) 108 | // select {} 109 | } 110 | -------------------------------------------------------------------------------- /eddystone.go: -------------------------------------------------------------------------------- 1 | package beacon 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "github.com/go-ble/ble" 11 | "github.com/go-ble/ble/examples/lib/dev" 12 | eddystone "github.com/suapapa/go_eddystone" 13 | ) 14 | 15 | const ( 16 | advTypeAllUUID16 = 0x03 // Complete List of 16-bit Service Class UUIDs 17 | advTypeServiceData16 = 0x16 // Service Data - 16-bit UUID 18 | ) 19 | 20 | const ( 21 | advFlagGeneralDiscoverable = 0x02 22 | advFlagLEOnly = 0x04 23 | ) 24 | 25 | type EddyStoneBeacon struct { 26 | //Output frame data for eddystone beacon 27 | OutFrame *eddystone.Frame 28 | 29 | //Tx power is the received power at 0 meters, in dBm, and the value ranges from -100 dBm to +20 dBm to a resolution of 1 dBm. 30 | // 31 | //Note to developers: 32 | // The best way to determine the precise value to put into this field is to measure the actual output of 33 | // your beacon from 1 meter away and then add 41dBm to that. 41dBm is the signal loss that occurs over 1 meter. 34 | PowerLevel int 35 | } 36 | 37 | //Create a Eddystone beacon with URL frame (physical web) 38 | // url: It need include with "http://" or "https://" 39 | // PowerLevel: The power level adjustment, refer PowerLevel description 40 | // Ex: NewEddystoneURLBeacon("http://google.com", -20) 41 | func NewEddystoneURLBeacon(url string, powerLevel int) *EddyStoneBeacon { 42 | eb := new(EddyStoneBeacon) 43 | eb.PowerLevel = powerLevel 44 | eb.addURL(url) 45 | return eb 46 | } 47 | 48 | //Create a Eddystone beacon with UID frame (10 bytes for namesapce, 6 bytes for instance) 49 | // Namespace: 10 bytes for UID namespace 50 | // Instance: 6 bytes for instance 51 | // PowerLevel: The power level adjustment, refer PowerLevel description 52 | // Ex: NewEddystoneUIDBeacon("00010203040506070809", "aabbccddeeff", -20) 53 | func NewEddystoneUIDBeacon(namespace, instance string, powerLevel int) *EddyStoneBeacon { 54 | eb := new(EddyStoneBeacon) 55 | eb.PowerLevel = powerLevel 56 | eb.addUID(namespace, instance) 57 | return eb 58 | } 59 | 60 | //Create a Eddystone beacon with TLM frame (for tansfer telemetry) 61 | // BATT: The battery voltage 62 | // TEMP: Beacon temperature is the temperature in degrees Celsius 63 | // AdvCnt: Count of advertisement frames of all types emitted by the beacon since power-up or reboot 64 | // SecCnt: Counter that represents time since beacon power-up or reboot. 65 | // PowerLevel: The power level adjustment, refer PowerLevel description 66 | // Ex: NewEddystoneUIDBeacon(500, 22.0, 100, 1000, -20) 67 | func NewEddystoneTLMBeacon(batt uint16, temp float32, advCnt, secCnt uint32, powerLevel int) *EddyStoneBeacon { 68 | eb := new(EddyStoneBeacon) 69 | eb.PowerLevel = powerLevel 70 | eb.addTLM(batt, temp, advCnt, secCnt) 71 | return eb 72 | } 73 | 74 | func (eb *EddyStoneBeacon) addTLM(batt uint16, temp float32, advCnt, secCnt uint32) { 75 | f, err := eddystone.MakeTLMFrame(batt, temp, advCnt, secCnt) 76 | if err != nil { 77 | panic(err) 78 | } 79 | eb.OutFrame = &f 80 | } 81 | 82 | func (eb *EddyStoneBeacon) addUID(ns, inst string) { 83 | f, err := eddystone.MakeUIDFrame(ns, inst, eb.PowerLevel) 84 | if err != nil { 85 | panic(err) 86 | } 87 | eb.OutFrame = &f 88 | } 89 | 90 | func (eb *EddyStoneBeacon) addURL(url string) { 91 | f, err := eddystone.MakeURLFrame(url, eb.PowerLevel) 92 | if err != nil { 93 | panic(err) 94 | } 95 | eb.OutFrame = &f 96 | } 97 | 98 | // Start to Advertise your beacon, it is block API. 99 | func (eb *EddyStoneBeacon) Advertise() { 100 | // a := &gatt.AdvPacket{} 101 | // a.AppendFlags(advFlagGeneralDiscoverable | advFlagLEOnly) 102 | // a.AppendField(advTypeAllUUID16, eddystone.SvcUUIDBytes) 103 | // a.AppendField(advTypeServiceData16, append(eddystone.SvcUUIDBytes, *eb.OutFrame...)) 104 | 105 | // fmt.Println(a.Len(), a) 106 | 107 | fmt.Printf("Eddystone data: %s\n", hex.EncodeToString(*eb.OutFrame)) 108 | d, err := dev.NewDevice("default") 109 | if err != nil { 110 | log.Fatalf("Failed to open device, err: %s", err) 111 | } 112 | 113 | ble.SetDefaultDevice(d) 114 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), 30*time.Second)) 115 | fmt.Printf("Eddystone advertising .... \n") 116 | ble.AdvertiseIBeaconData(ctx, *eb.OutFrame) 117 | // // Register optional handlers. 118 | // d.Handle( 119 | // gatt.CentralConnected(func(c gatt.Central) { fmt.Println("Connect: ", c.ID()) }), 120 | // gatt.CentralDisconnected(func(c gatt.Central) { fmt.Println("Disconnect: ", c.ID()) }), 121 | // ) 122 | 123 | // // A mandatory handler for monitoring device state. 124 | // onStateChanged := func(d gatt.Device, s gatt.State) { 125 | // fmt.Printf("State: %s\n", s) 126 | // switch s { 127 | // case gatt.StatePoweredOn: 128 | // d.Advertise(a) 129 | // default: 130 | // log.Println(s) 131 | // } 132 | // } 133 | 134 | // d.Init(onStateChanged) 135 | // select {} 136 | } 137 | --------------------------------------------------------------------------------