GATT library directly reading characteristic values without iterating through services - linux

I'm trying to use go on a raspberry pi to query bluetooth low energy devices. It's functional, I can connect to the device I want and iterate through the services and characteristics of the connected device. Now I'm just trying to streamline things and just read/write the values I'm interested in. It isn't working.
Code:
func onPeriphConnected(p gatt.Peripheral, err error) {
fmt.Println("Connected")
defer p.Device().CancelConnection(p)
if err := p.SetMTU(500); err != nil {
fmt.Printf("Failed to set MTU, err: %s\n", err)
}
batteryServiceId := gatt.MustParseUUID("180f")
// Direct read attempt (not working)
batterySerivce := gatt.NewService(batteryServiceId)
batteryLevelUUID := gatt.MustParseUUID("2a19")
batteryChar := gatt.NewCharacteristic(batteryLevelUUID,batterySerivce,gatt.Property(0x12),0,0)
e, err := p.ReadCharacteristic(batteryChar)
if err != nil {
fmt.Printf("Failed to read battery level, err: %s\n", err)
} else {
fmt.Println(e)
}
// iterate services read (working)
ss, err := p.DiscoverServices(nil)
if err != nil {
fmt.Printf("Failed to discover services, err: %s\n", err)
return
}
for _, s := range ss {
if(s.UUID().Equal(batteryServiceId)) {
fmt.Println("Found the battery service")
// Discovery characteristics
cs, err := p.DiscoverCharacteristics(nil, s)
if err != nil {
fmt.Printf("Failed to discover characteristics, err: %s\n", err)
continue
}
for _, c := range cs {
msg := " Characteristic " + c.UUID().String()
if len(c.Name()) > 0 {
msg += " (" + c.Name() + ")"
}
msg += "\n properties " + c.Properties().String()
fmt.Println(msg)
if (c.Properties() & gatt.CharRead) != 0 {
b, err := p.ReadCharacteristic(c)
if err != nil {
fmt.Printf("Failed to read characteristic, err: %s\n", err)
continue
}
fmt.Printf(" value %x\n", b)
}
}
}
}
}
Results:
Connected
[10 0 0 1]
Found the battery service
Characteristic 2a19 (Battery Level)
properties read notify
value 53
You can see where I expect to get a hex value of 53 I'm instead getting an array of [10 0 0 1]. I'm pretty new to go so I'm probably missing something here or just assembling my read incorrectly. Any pointers are much appreciated. Thanks!

A link to the appropriate documentation would be advisable. I don't know if this link is correct as there seem to be multiple different versions of package gatt.
Edit: see also https://godoc.org/github.com/paypal/gatt/examples/service#NewBatteryService at https://github.com/paypal/gatt/blob/master/examples/service/battery.go, which appears to show the right way of creating a battery service directly. Original answer below.
Having had a quick scan through said documentation, two things leap out at me:
The battery level ID is 2a19. The battery service ID is 180f. You use:
batteryServiceId := gatt.MustParseUUID("180f")
batterySerivce := gatt.NewService(batteryServiceId)
batteryChar := gatt.NewCharacteristic(batteryLevelUUID, batterySerivce,
gatt.Property(0x12), 0, 0)
e, err := p.ReadCharacteristic(batteryChar)
(I kept variable name spellings, but added a bit of white space to fit better on the StackOverflow display.) You never call NewDescriptor nor either of AddDescriptor or SetDescriptors on batteryChar. Are such calls required? I don't know; the documentation doesn't say. But the call that works uses DiscoverServices followed by DiscoverCharacteristics, which perhaps does create these (documented but undescribed) Descriptors. They look like they interpose themselves in value-oriented operations, so they might be critical.
Looking further at the code, after or instead of creating a characteristic directly, I think you do have to at least link the characteristic back into the service. The right call might be AddCharacteristic or SetCharacteristics. See this chunk of code in DiscoverCharacteristics.
(Minor.) gatt.Property(0x12) is definitely the wrong way to construct the constant. You probably should use:
gatt.Property.CharRead | gatt.Property.CharNotify

Related

Query for PageRanges of a Managed Disk Incremental Snapshot returns zero changes

We have a solution which takes Incremental Snapshots of all the disks of a virtual machine. Once the snapshot is created, the solution queries it's page ranges to get the changed data.
The issue which we are facing currently is that the page ranges are returned as empty even when it's a first snapshot for the disk and the disk has data. This happens intermittently for OS as well as Data disks. Strangely, if a virtual machine has multiple disks, the page ranges return appropriate information for few and empty for others.
We are using Virtual Machine Disk Incremental Snapshot Operation as below. The solution makes use of GO SDK of Azure for making these operations. Below is the sample code for the operations.
// Prepare azure snapshot api client
snapClient, err := armcompute.NewSnapshotsClient(subscriptionID, azureCred, nil)
// Configure snapshot parameters
snapParams := armcompute.Snapshot{
Location: to.Ptr(location), // Snapshot location
Name: &snapshotName,
Properties: &armcompute.SnapshotProperties{
CreationData: &armcompute.CreationData{
CreateOption: to.Ptr(armcompute.DiskCreateOptionCopy),
SourceResourceID: to.Ptr(getAzureIDFromName(constant.AzureDiskURI, subscriptionID, resourceGroupName, diskID, "")),
}, // Disk ID for which the snapshot needs to be created
Incremental: to.Ptr(true),
},
}
// Create Disk Snapshot (Incremental)
snapPoller, err := snapClient.BeginCreateOrUpdate(ctx, resourceGroupName, snapshotName, snapParams, nil)
if err != nil {
return nil, err
}
Once the snapshot is created successfully, we prepare the changed areas for the snapshot using PageRanges feature as below.
// Grant Snapshot Access
resp, err := snapClient.BeginGrantAccess(ctx, resourceGroupName, snapshotId), armcompute.GrantAccessData{
Access: &armcompute.PossibleAccessLevelValues()[1], // 0 is none, 1 is read and 2 is write
DurationInSeconds: to.Ptr(duration), // 1 hr
}, nil)
if err != nil {
return "", err
}
grantResp, err := resp.PollUntilDone(ctx, nil)
if err != nil {
return "", err
}
currentSnapshotSAS = grantResp.AccessSAS
// Create Page Blob Client
pageBlobClient, err := azblob.NewPageBlobClientWithNoCredential(currentSnapshotSAS, nil)
pageOption := &azblob.PageBlobGetPageRangesDiffOptions{}
pager := pageBlobClient.GetPageRangesDiff(pageOption)
if err != nil {
return nil, err
}
// Gather Page Ranges for all the changed data
var pageRange []*azblob.PageRange
if pager.NextPage(ctx) {
resp := pager.PageResponse()
pageRange = append(pageRange, resp.PageRange...)
}
// Loop through page ranges and collect all changed data indexes
var changedAreaForCurrentIter int64
changedAreasString := ""
for _, page := range pageRanges {
length := (*page.End + 1) - *page.Start
changedAreaForCurrentIter = changedAreaForCurrentIter + length
changedAreasString = changedAreasString + strconv.FormatInt(*page.Start, 10) + ":" + strconv.FormatInt(length, 10) + ","
}
zap.S().Debug("Change areas : [" changedAreasString "]")
It is this when the Change areas is coming in as empty. We have checked Disk properties for the ones which are successful and which failed and they are all the same. There is no Lock configured on the disk.
Following are the SDK versions which we are using.
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
Can someone please provide pointers explaining what are the factors which could make this problem appear intermittently.

Get last n files in directory sorted by timestamp without listing all files

I am trying to get get last N files from a directory sorted by Creation/Modification time.
I am currently using this code:
files, err := ioutil.ReadDir(path)
if err != nil {
return 0, err
}
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().Before(files[j].ModTime())
})
The problem here is that the expected amount of files in this directory is ~ 2mil and when I get all of them in a slice, it consumes a lot of memory ~ 800mb. Also it is not sure when the GC will clean the memory.
Is there other way where I can get the last N files in the directory sorted by ts without reading and consuming all of the files in the memory?
My first answer using filepath.Walk was still allocating a huge chunk of memory as #Marc pointed out. So here an improved algorithm.
Note: This is not an optimized algorithm. It's just about providing an idea on how takle the problem.
maxFiles := 5
batch := 100 // optimize to find good balance
dir, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
var files []os.FileInfo
for {
fs, err := dir.Readdir(batch)
if err != nil {
log.Println(err)
break
}
for _, fileInfo := range fs {
var lastFile os.FileInfo
if maxFiles <= len(files) {
lastFile = files[len(files)-1]
}
if lastFile != nil && fileInfo.ModTime().After(lastFile.ModTime()) {
break
}
files = append(files, fileInfo)
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().Before(files[j].ModTime())
})
if maxFiles < len(files) {
files = files[:maxFiles]
}
break
}
}
The basic idea is to only keep the oldest max X files in memory and discard the newer ones immediately or as soon as an older file pushes them out of the list.
Instead of a slice it might be helpful to look into using a btree (as it is sorted internally) or a double linked list. You'll have to do some benchmarking to figure out what is optimal.

Notification feed following Flat feed isn't showing activities

I have a notification feed like NOTIFICATIONS:userID and I have a flat feed GLOBAL:domain.
The notification feed is set up to follow the flat feed, but when I push activities to the flat feed they are not coming through to the notification feed. I can't get them to come through the react components or making the API calls directly. Any items in the notification feed come through fine, but not the flat feed.
Is there anything I would have missed when setting up the feeds to make this possible? I'm not sure why it isn't working.
Here's the code used to call getstream:
// AddNotification writes a feed notification to the provided feed.
func (c *Client) AddNotification(feedID, actor string, n *feed.Notification) error {
keys := map[string]bool{}
feeds := make([]stream.Feed, 0)
for _, s := range n.Streams {
if s == feed.STREAM_NONE {
continue
}
if _, ok := keys[s.String()]; ok {
continue
}
f, err := c.getstream.FlatFeed(s.String(), feedID)
if err != nil {
return errors.Wrapf(err, "failed to get feed %s", feedID)
}
keys[s.String()] = true
feeds = append(feeds, f)
}
extra, err := getExtraFromString(n.Content)
if err != nil {
return errors.Wrap(err, "failed to marshal extra content")
}
appliesAt, err := time.FromProtoTS(n.GetAppliesAt())
if err != nil {
return errors.Wrap(err, "failed to cast applies at time")
}
activity := stream.Activity{
Actor: actor,
Verb: n.GetVerb(),
Object: n.GetObject(),
Extra: extra,
ForeignID: n.GetIdempotentKey(),
Time: stream.Time{Time: appliesAt},
}
log.WithFields(log.Fields{
"activity": activity,
"feeds": keys,
}).Debug("sending request to stream.io")
if err = c.getstream.AddToMany(activity, feeds...); err != nil {
return errors.Wrap(err, "error while feeding to stream.io")
}
return nil
}
Just to explain the code a bit. We have a feed.Notification type that allows you to specify what we've called "streams", these are just types that represent the slugs.
In this case, I'm using the GLOBAL:domain feed, which the user's NOTIFICATION:userID feed is set up to follow.
From batch add docs:
Activities added using this method are not propagated to followers. That is, any other Feeds that follow the Feed(s) listed in the API call will not receive the new Activity.
If you're using batching, you need to specify all feeds you want to add the activity for. Another way is that you can add to feeds one by one to push to followers.

How to get Resident Set Size (RSS)

I need to know physical memory consumption of my Go process in Linux.
What I need is Resident Set Size (RSS).
runtime.ReadMemStats is not suitable because there is no way to get a size of a portion of a virtual memory that is resident.
syscall.Getrusage is not suitable because it returns only Maxrss that is maximal RSS during process life.
How can I query RSS in Go?
You can parse /proc/self/statm file. It contains only integers so it is easy to parse. Second field is RSS measured in pages.
func getRss() (int64, error) {
buf, err := ioutil.ReadFile("/proc/self/statm")
if err != nil {
return 0, err
}
fields := strings.Split(string(buf), " ")
if len(fields) < 2 {
return 0, errors.New("Cannot parse statm")
}
rss, err := strconv.ParseInt(fields[1], 10, 64)
if err != nil {
return 0, err
}
return rss * int64(os.Getpagesize()), err
}

Handling Dynamic Errors In Go (Specifically database/sql Package)

Using the database/sql package in go for things like sql.Exec will return dynamically generated, unreferenced errors such as
"Error 1062: Duplicate entry '192' for key 'id'"
The problem is that it can also return errors such as
"Error 1146: Table 'tbl' doesn't exist"
From the same call to sql.Exec
How can I tell the difference between these two errors without
String comparison, or
Pattern matching for error code
Or are those idiomatic viable solutions for this problem?
database/sql package does not solve this problem. It's driver specific. For example, for mysql you can use:
if mysqlError, ok := err.(*mysql.MySQLError); ok {
if mysqlError.Number == 1146 {
//handling
}
}
Also, you can find some error constant package, like mysqlerr from VividCortex, and use it:
if mysqlError, ok := err.(*mysql.MySQLError); ok {
if mysqlError.Number == mysqlerr.ER_NO_SUCH_TABLE {
//handling
}
}
It's not much better than pattern matching, but seems to be more idiomatic.
I think there's no idiomatic solution, but I wrote a simple function for getting the error number, so you can easily compare them.
In this solution I assume that the construction of the error message is always the same: "Error -some number here-: Error description".
If there's no number in the error or something went wrong it returns 0.
func ErrorCode(e error) int {
err := e.Error() //the description of the error
if len(err) < 6 { //if its too small return 0
return 0
}
i := 6 //Skip the part "Error "
for ; len(err) > i && unicode.IsDigit(rune(err[i])); i++ {
} // Raising i until we reach the end of err or we reach the end of error code
n, e := strconv.Atoi(string(err[6:i])) //convert it to int
if e != nil {
return 0 //something went wrong
}
return n //return the error code
}
Go playground link: http://play.golang.org/p/xqhVycsuyI

Resources