| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package nmble |
| |
| import ( |
| "fmt" |
| |
| log "github.com/Sirupsen/logrus" |
| |
| . "mynewt.apache.org/newtmgr/nmxact/bledefs" |
| "mynewt.apache.org/newtmgr/nmxact/nmxutil" |
| "mynewt.apache.org/newtmgr/nmxact/sesn" |
| ) |
| |
| type AdvertiseCfg struct { |
| // Mandatory |
| OwnAddrType BleAddrType |
| ConnMode BleAdvConnMode |
| DiscMode BleAdvDiscMode |
| ItvlMin uint16 |
| ItvlMax uint16 |
| ChannelMap uint8 |
| FilterPolicy BleAdvFilterPolicy |
| HighDutyCycle bool |
| AdvFields BleAdvFields |
| RspFields BleAdvFields |
| SesnCfg sesn.SesnCfg |
| |
| // Only required for direct advertisements |
| PeerAddr *BleAddr |
| } |
| |
| func NewAdvertiseCfg() AdvertiseCfg { |
| return AdvertiseCfg{ |
| OwnAddrType: BLE_ADDR_TYPE_RANDOM, |
| ConnMode: BLE_ADV_CONN_MODE_UND, |
| DiscMode: BLE_ADV_DISC_MODE_GEN, |
| } |
| } |
| |
| type Advertiser struct { |
| bx *BleXport |
| stopChan chan struct{} |
| stoppedChan chan struct{} |
| } |
| |
| func NewAdvertiser(bx *BleXport) *Advertiser { |
| return &Advertiser{ |
| bx: bx, |
| } |
| } |
| |
| func (a *Advertiser) fields(f BleAdvFields) ([]byte, error) { |
| r := BleAdvFieldsToReq(f) |
| |
| bl, err := a.bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return nil, err |
| } |
| defer a.bx.RemoveListener(bl) |
| |
| return advFields(a.bx, bl, r) |
| } |
| |
| func (a *Advertiser) setAdvData(data []byte) error { |
| r := NewBleAdvSetDataReq() |
| r.Data = BleBytes{data} |
| |
| bl, err := a.bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return err |
| } |
| defer a.bx.RemoveListener(bl) |
| |
| if err := advSetData(a.bx, bl, r); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (a *Advertiser) setRspData(data []byte) error { |
| r := NewBleAdvRspSetDataReq() |
| r.Data = BleBytes{data} |
| |
| bl, err := a.bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return err |
| } |
| defer a.bx.RemoveListener(bl) |
| |
| if err := advRspSetData(a.bx, bl, r); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (a *Advertiser) advertise(cfg AdvertiseCfg) (uint16, *Listener, error) { |
| r := NewBleAdvStartReq() |
| |
| r.OwnAddrType = cfg.OwnAddrType |
| r.DurationMs = 0x7fffffff |
| r.ConnMode = cfg.ConnMode |
| r.DiscMode = cfg.DiscMode |
| r.ItvlMin = cfg.ItvlMin |
| r.ItvlMax = cfg.ItvlMax |
| r.ChannelMap = cfg.ChannelMap |
| r.FilterPolicy = cfg.FilterPolicy |
| r.HighDutyCycle = cfg.HighDutyCycle |
| r.PeerAddr = cfg.PeerAddr |
| |
| bl, err := a.bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return 0, nil, err |
| } |
| |
| connHandle, err := advStart(a.bx, bl, a.stopChan, r) |
| if err != nil { |
| a.bx.RemoveListener(bl) |
| if !nmxutil.IsXport(err) { |
| // The transport did not restart; always attempt to cancel the |
| // advertise operation. In some cases, the host has already stopped |
| // advertising and will respond with an "ealready" error that can be |
| // ignored. |
| if err := a.stopAdvertising(); err != nil { |
| log.Debugf("Failed to cancel advertise in progress: %s", |
| err.Error()) |
| } |
| } |
| return 0, nil, err |
| } |
| |
| return connHandle, bl, nil |
| } |
| |
| func (a *Advertiser) stopAdvertising() error { |
| r := NewBleAdvStopReq() |
| |
| bl, err := a.bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return err |
| } |
| defer a.bx.RemoveListener(bl) |
| |
| return advStop(a.bx, bl, r) |
| } |
| |
| func (a *Advertiser) buildSesn(cfg AdvertiseCfg, connHandle uint16, |
| bl *Listener) (sesn.Sesn, error) { |
| |
| s, err := NewBleSesn(a.bx, cfg.SesnCfg) |
| if err != nil { |
| return nil, err |
| } |
| |
| if err := s.OpenConnected(connHandle, bl); err != nil { |
| return nil, err |
| } |
| |
| return s, nil |
| } |
| |
| func (a *Advertiser) Start(cfg AdvertiseCfg) (sesn.Sesn, error) { |
| var advData []byte |
| var rspData []byte |
| var connHandle uint16 |
| var bl *Listener |
| var err error |
| |
| fns := []func() error{ |
| // Convert advertising fields to data. |
| func() error { |
| advData, err = a.fields(cfg.AdvFields) |
| return err |
| }, |
| |
| // Set advertising data. |
| func() error { |
| return a.setAdvData(advData) |
| }, |
| |
| // Convert response fields to data. |
| func() error { |
| rspData, err = a.fields(cfg.RspFields) |
| return err |
| }, |
| |
| // Set response data. |
| func() error { |
| return a.setRspData(rspData) |
| }, |
| |
| // Advertise |
| func() error { |
| connHandle, bl, err = a.advertise(cfg) |
| return err |
| }, |
| } |
| |
| a.stopChan = make(chan struct{}) |
| a.stoppedChan = make(chan struct{}) |
| |
| defer func() { |
| a.stopChan = nil |
| close(a.stoppedChan) |
| }() |
| |
| if err := a.bx.AcquireSlave(a); err != nil { |
| return nil, err |
| } |
| defer a.bx.ReleaseSlave() |
| |
| for _, fn := range fns { |
| // Check for abort before each step. |
| select { |
| case <-a.stopChan: |
| return nil, fmt.Errorf("advertise aborted") |
| default: |
| } |
| |
| if err := fn(); err != nil { |
| return nil, err |
| } |
| } |
| |
| return a.buildSesn(cfg, connHandle, bl) |
| } |
| |
| func (a *Advertiser) Stop() error { |
| stopChan := a.stopChan |
| if stopChan == nil { |
| return fmt.Errorf("advertiser already stopped") |
| } |
| close(stopChan) |
| |
| a.bx.StopWaitingForSlave(a, fmt.Errorf("advertise aborted")) |
| a.stopAdvertising() |
| |
| // Block until abort is complete. |
| <-a.stoppedChan |
| |
| return nil |
| } |