280 Commits

Author SHA1 Message Date
Elara6331 37c61695aa Update maintainer name 2024-09-23 13:46:15 +00:00
vchigrin 243826bd67 Fix crash in filesystem API (#69)
This help me fix #issues/64

Reviewed-on: Elara6331/itd#69
Reviewed-by: Elara <elara@elara.ws>

Fixes #64
Co-authored-by: Vyacheslav Chigrin <vyacheslav.chigrin@izba.dev>
Co-committed-by: Vyacheslav Chigrin <vyacheslav.chigrin@izba.dev>
2024-07-04 03:25:39 +00:00
Elara6331 cb5abc05e5 Remove debug code 2024-06-04 15:17:24 -07:00
Elara6331 39a3603c8e Fix Nominatim geocoding 2024-06-04 15:00:42 -07:00
Elara6331 e6b36494e7 Prevent operations on a file once it's closed 2024-04-14 11:46:43 -07:00
Elara6331 02532437ea Fix cloud area calculation 2024-04-13 21:36:57 -07:00
Elara6331 7e68d5541c Add rewritten infinitime abstraction and integrate it into ITD 2024-04-13 21:20:12 -07:00
Elara6331 2a8013e63e Update infinitime dependency 2024-04-01 21:54:24 -07:00
Elara6331 6904e95913 Fix NewHeader calls for infinitime update 2024-04-01 20:41:42 -07:00
Elara6331 c0937918b1 Update infinitime dependency 2024-04-01 20:36:25 -07:00
Elara6331 57d5eafbe7 Update LURE badges 2024-01-20 04:56:54 +00:00
Elara6331 d93672c997 Add FUNDING.yml 2023-12-15 16:11:00 -08:00
Elara6331 35163198c9 Add swh badge to README 2023-10-20 17:23:29 +00:00
Elara6331 881a103c56 Fix README LURE badges 2023-10-15 19:30:21 +00:00
Elara6331 395cded975 Build for riscv64 2023-08-05 12:54:44 -07:00
Elara6331 254b675f2c Fix invalid user agent error 2023-06-28 21:53:00 -07:00
Elara6331 669788034f Run formatter 2023-06-28 21:49:44 -07:00
Elara6331 ac7c626f56 Use select statement to prevent blocking on context status check 2023-06-28 21:49:07 -07:00
Elara6331 1c623051ec Fix README links 2023-05-22 19:34:31 +00:00
Elara6331 cec539f85b Add LURE badges to README 2023-05-16 20:07:58 +00:00
Elara6331 bec703c300 Update README status badge 2023-04-26 22:38:56 -07:00
Elara6331 25220cf334 Update goreleaser config for new name/domain 2023-04-26 22:11:24 -07:00
Elara6331 858edb0f55 Only send call notification for incoming calls 2023-04-25 16:13:38 -07:00
Elara6331 9998915959 Update domain 2023-04-20 19:54:58 -07:00
Elara6331 e0023907a0 Update go.mod domain 2023-04-20 19:15:10 -07:00
Elara6331 f974abed3b Update drpc library version 2023-03-28 13:27:14 -07:00
Elara6331 ebca255fa8 Remove replace directive for drpc 2023-03-28 13:25:26 -07:00
Elara6331 f7ac77273e Gracefully shut down each component before exiting 2023-03-26 14:34:29 -07:00
Elara6331 5ce83902a4 Use EINVAL for Invalid Offset error in FUSE 2023-03-26 13:01:25 -07:00
Elara6331 ee5cb174fb Use type switch for syscallErr 2023-03-25 16:03:56 -07:00
Elara6331 6667ba576c Set some log levels to Debug 2023-03-25 15:53:46 -07:00
Elara6331 92acdee152 Removed unreachable code 2023-03-25 15:52:46 -07:00
Elara6331 a90069999d Create new type for node kinds 2023-03-25 15:52:13 -07:00
Elara6331 6d2469311e Mention FUSE support in README 2023-03-25 15:27:28 -07:00
Elara6331 510f183db2 Run formatter 2023-03-25 15:24:46 -07:00
yannickulrich 77680704e1 Added FUSE support (#55)
This exposes the watches' file system over FUSE. This way, we can access files on the watch without having to go through `itctl` or developing 3rd party tools.

**Features**

- [x] read/write access to the file system
- [x] read access to momentary sensor data
- [x] live access to sensor data (i.e. WatchMotion rather than Motion)
- [x] configuration of mount point

Co-authored-by: Yannick Ulrich <yannick.ulrich@durham.ac.uk>
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/55
Co-authored-by: yannickulrich <yannick.ulrich@protonmail.com>
Co-committed-by: yannickulrich <yannick.ulrich@protonmail.com>
2023-03-25 22:23:51 +00:00
Elara6331 35701b1396 Switch from zerolog to go.arsenm.dev/logger in itctl 2023-01-04 15:17:14 -08:00
Elara6331 e858c43b5e Switch from zerolog to go.arsenm.dev/logger 2023-01-04 15:06:05 -08:00
Elara6331 6337fde64e Upgrade go.arsenm.dev/drpc 2023-01-04 14:25:58 -08:00
Elara6331 4dec1d70b8 Add error handling for RPC registration functions 2023-01-03 16:47:25 -08:00
Elara6331 19a9f64525 Move multiplexing code into separate module 2023-01-03 13:06:38 -08:00
Elara6331 053f8f50d7 Update itgui screenshots 2023-01-03 09:39:21 -08:00
Elara6331 33f772d80c Add doc comments 2023-01-03 09:30:04 -08:00
Elara6331 a787e58128 Use multiplexed connection in NewFromConn() 2023-01-03 09:24:36 -08:00
Elara6331 b8746cc924 Run formatter 2023-01-03 09:18:57 -08:00
Elara6331 3d02ff5b64 Use correct paths in README 2023-01-03 09:16:59 -08:00
Elara6331 86a77f6f1f Properly close multiplexed streams 2023-01-03 09:15:38 -08:00
Elara6331 94ec82c4a6 Start separate goroutine for multiplexed stream handling 2023-01-03 01:02:48 -08:00
Elara6331 b656c69350 Add connection multiplexing, fixing itgui 2023-01-03 00:54:00 -08:00
Elara6331 01919e67a3 Fix dependencies 2023-01-02 23:06:04 -08:00
Elara6331 c9444e276a Restructure and revise README 2023-01-02 22:42:12 -08:00
Elara6331 19c87ddde1 Remove itgui CI config 2023-01-02 22:32:04 -08:00
Elara6331 d41872ab64 Switch to autogenerated DRPC framework 2023-01-02 22:30:17 -08:00
Hunman 15b5d2888e Warn when Koanf read fails (#47)
Figured out the problem in issue #32, the toml file syntax was invalid (I had `'` instead of `"` at some values), but there was nothing in the logs about it.

Moved the config reading (and watching) into the same function, which logs the error as a warning.

I wanted to try moving the whole `if` into a separate function, but I kept getting errors when I tried to extract the `path` from the `File`, so I have that attempt in a separate branch not in this pull request: https://gitea.arsenm.dev/Hunman/itd/commit/5a84bf81489d3dc57f197f5feef5521950645ba5

Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/47
Co-authored-by: Hunman <sanyi.exe@gmail.com>
Co-committed-by: Hunman <sanyi.exe@gmail.com>
2023-01-02 09:05:23 +00:00
Elara6331 131a16df6f Add tests 2022-12-08 01:16:00 -08:00
Elara6331 9f0ca5a7df Move mpris out of pkg directory and run gofumpt 2022-11-24 17:36:25 -08:00
Elara6331 b3be8e7027 Fix goreleaser aur config 2022-11-24 16:24:45 -08:00
Elara6331 6a92eba062 Add archlinux package to goreleaser config 2022-11-24 16:23:03 -08:00
Elara6331 33a01a64d6 Add itgui.desktop to goreleaser and fix itgui permissions 2022-11-24 16:20:11 -08:00
Elara6331 919375d214 Add itgui.desktop 2022-11-24 16:18:26 -08:00
Elara6331 ff0ead0343 Prepare for itgui cross-compilation 2022-11-24 16:16:25 -08:00
Elara6331 f3f66176b8 Switch version.txt target to use go generate 2022-11-22 03:23:14 +00:00
Elara6331 2c733edeec Merge pull request 'Move mpris implementation from infinitime library to itd, where it really belongs' (#41) from FloralExMachina/itd:master into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/41
2022-11-22 02:24:51 +00:00
razorkitty 8ee8c39aa7 fixed type in comment about DBus 2022-11-22 02:23:03 +00:00
razorkitty 5699375b2a copy mpris implementation from infinitime library to itd, where it really belongs
moved dbus.go to an internal utils package
added context function parameter to initMusicCtrl and updated main.go to pass it
updated calls.go, maps.go, music.go, and notifs.go to use utils package for getting a dus connection
2022-11-21 19:59:54 +00:00
Elara6331 7ba643888c Remove gitm config as it's no longer needed 2022-11-19 15:38:23 -08:00
Elara6331 23f9344378 Remove pactl dependencies (Arsen6331/infinitime#6) 2022-11-19 15:29:10 -08:00
Elara6331 5284619ac3 Merge pull request 'update itctl usage screen to current output' (#39) from mashuptwice/itd:master into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/39
2022-11-19 04:04:31 +00:00
mashuptwice 954e653092 update itctl usage screen to current output 2022-11-19 03:55:22 +00:00
Elara6331 1c0c89b3d4 Switch badge to self-hosted CI 2022-11-18 07:50:21 +00:00
Elara6331 16adc3d4c7 Add go generate script for calculating version number 2022-11-17 21:27:36 -08:00
Elara6331 6dd1faafac Add woodpecker config 2022-11-18 05:02:37 +00:00
Elara6331 d6fa98fdac Add resource loading to ITD FS tab 2022-11-15 19:20:34 -08:00
Elara6331 d84f34f5b9 Mention navigation support in README 2022-11-07 12:24:55 -08:00
Elara6331 87bca3270a Add navigation support via PureMaps 2022-11-07 12:22:14 -08:00
Elara6331 9298d7c920 Update infinitime library 2022-10-25 12:38:02 -07:00
Elara6331 ecca2b320e Update infinitime library 2022-10-20 01:42:23 -07:00
Elara6331 5301546e7e Update infinitime library 2022-10-17 12:50:51 -07:00
Elara6331 1231dd5308 Add warning if current InfiniTime doesn't support BLE FS (#29) 2022-10-17 12:40:51 -07:00
Elara6331 cdc5d22867 Update infinitime library 2022-10-17 12:24:17 -07:00
Elara6331 be57cdeea4 Handle error events in itctl res load command (#29) 2022-10-17 12:23:06 -07:00
Elara6331 53bc192607 Merge pull request 'Add resource loading to ITD' (#28) from resource-loading into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/28
2022-10-16 20:17:49 +00:00
Elara6331 5719e77b59 Add resource loading as part of DFU 2022-10-16 13:17:12 -07:00
Elara6331 aefe6a82ff Close channel once resource uploading complete 2022-10-16 12:42:59 -07:00
Elara6331 a04c95b0be Add -r for rm and -p for mkdir 2022-09-03 16:28:25 -07:00
Elara6331 5dfcd33563 Remove example comments from goreleaser config 2022-09-01 15:09:49 -07:00
Elara6331 a1c6e08987 Fix file extension of Alpine example 2022-09-01 15:02:11 -07:00
Elara6331 e82a9e3f18 Add --allow-untrusted to Alpine example 2022-09-01 15:00:03 -07:00
Elara6331 61d14857d8 Add installation instructions for major distros 2022-09-01 14:58:33 -07:00
Elara6331 4c34634220 Remove download binary badge and add new AUR badge 2022-09-01 14:33:23 -07:00
Elara6331 828d4944f4 Allow automatic release to the AUR 2022-09-01 11:50:41 -07:00
Elara6331 2eb25219d9 Update CI badge 2022-09-01 03:14:05 -07:00
Elara6331 2ab9fbcb80 Add GoReleaser config 2022-09-01 01:52:46 -07:00
Elara6331 a178319e28 Add resource loading to itctl 2022-08-30 13:01:36 -07:00
Elara6331 ec6d216346 Add LoadResources() to API 2022-08-30 12:13:22 -07:00
Elara6331 e50c8430be Improve itgui compilation documentation (Fixes #24) 2022-08-29 13:28:52 -07:00
Elara6331 df6f9f1872 Update infinitime library and bluetooth library (Fixes #22) 2022-08-19 14:05:30 -07:00
Elara6331 74ef74f90d Revert #22 bandaid fix 2022-08-19 14:04:08 -07:00
Elara6331 f2d50d5bd9 Temporary bandaid fix for #22 2022-08-14 00:05:56 -07:00
Elara6331 d59e7af2d1 Fix comment above goroutine code 2022-07-31 02:40:46 -07:00
Elara6331 3c31bd2921 Fix bug where itctl doesn't exit on SIGINT/SIGTERM 2022-07-31 02:22:33 -07:00
Elara6331 900be6f2d0 Fix bug where help command doesn't show flags/subcommands 2022-07-31 02:15:42 -07:00
Elara6331 5cd395a41b Remove GOFLAGS from Makefile as the go tool already looks at that variable 2022-07-22 10:36:19 -07:00
Elara6331 bcf0d33531 Propagate context to lrpc 2022-05-12 17:14:34 -07:00
Elara6331 f5092cd2e1 Create and propagate contexts wherever possible 2022-05-11 13:24:12 -07:00
Elara6331 4250541891 Add metrics screenshot 2022-05-11 12:10:50 -07:00
Elara6331 cab8e4b089 Add metrics graphs to itgui 2022-05-10 23:37:58 -07:00
Elara6331 5043bb8cfe Add metrics collection via sqlite 2022-05-10 18:03:37 -07:00
Elara6331 74fbb79363 Update lrpc 2022-05-10 02:12:52 -07:00
Elara6331 5ece2415c5 Update Go compiler requirement 2022-05-09 21:46:03 -07:00
Elara6331 31c5c51b87 Allow API client to be made from connection 2022-05-07 21:23:42 -07:00
Elara6331 63cacded4a Update lrpc library 2022-05-07 15:12:29 -07:00
Elara6331 e7942b263d Add new itgui screenshots 2022-05-05 14:05:58 -07:00
Elara6331 263e8c1bce Rewrite itgui and add new screenshots 2022-05-05 14:00:49 -07:00
Elara6331 0429dc8197 Update infinitime library 2022-05-05 12:41:51 -07:00
Elara6331 095b82e2e9 Update lrpc for data race fixes 2022-05-04 16:16:28 -07:00
Elara6331 8723dddbc1 Update lrpc 2022-05-03 18:55:37 -07:00
Elara6331 2cda6e2bde Update infinitime library 2022-05-02 20:21:47 -07:00
Elara6331 711df70edc Allow changing bluetooth adapter ID 2022-05-02 20:17:38 -07:00
Elara6331 889d16fb0a Update lrpc 2022-05-02 16:28:25 -07:00
Elara6331 f67f653427 Fix lrpc response line number in README 2022-05-01 23:06:27 -07:00
Elara6331 4166903397 Update lrpc 2022-05-01 21:39:58 -07:00
Elara6331 ce6080e920 Remove version.txt on clean 2022-05-01 21:22:15 -07:00
Elara6331 f532a05c66 Only update version if version.txt does not exist 2022-05-01 21:21:22 -07:00
Elara6331 68d8588978 Update README to reflect recent changes 2022-05-01 21:16:47 -07:00
Elara6331 3689fcf889 Remove debug print 2022-05-01 20:56:14 -07:00
Elara6331 f1a7a87ef8 Remove the no-longer useful none type alias 2022-05-01 20:51:13 -07:00
Elara6331 e5e6bab7e3 Remove version.txt 2022-05-01 20:49:42 -07:00
Elara6331 d75d893409 Remove replace directive and fix firmware upgrade error 2022-05-01 20:40:30 -07:00
Elara6331 321afe0121 Fix bug where itctl could not be killed 2022-05-01 20:32:59 -07:00
Elara6331 6ba50fb7de Add context support and update lrpc 2022-05-01 15:22:28 -07:00
Elara6331 78e64fe3ed Remove now unnecessary DoneMap 2022-05-01 14:00:31 -07:00
Elara6331 51b8e581f7 Upgrade lrpc version 2022-05-01 13:59:40 -07:00
Elara6331 fe00e8bb65 Use default codec 2022-05-01 11:41:16 -07:00
Elara6331 a1ee021675 Switch to lrpc and use context to handle signals 2022-05-01 11:36:28 -07:00
Elara6331 c929635029 Use rpcxlite 2022-04-30 03:25:27 -07:00
Elara6331 c17ba102dd Add comments 2022-04-24 00:58:39 -07:00
Elara6331 0ae40d69bc Support bidirectional requests over gateway 2022-04-24 00:54:04 -07:00
Elara6331 44c89408d2 Add debug logs 2022-04-23 20:20:13 -07:00
Elara6331 dc87e144e0 Re-add watch commands to itctl 2022-04-23 18:46:49 -07:00
Elara6331 11af134444 Enable RPCX gateway 2022-04-23 11:29:16 -07:00
Elara6331 ac56dd5f57 Merge branch 'master' of ssh://192.168.100.62:2222/Arsen6331/itd 2022-04-22 19:22:32 -07:00
Elara6331 6dedd187d4 Improve error handling 2022-04-22 18:43:13 -07:00
Elara6331 4caa504db1 Remove old code comment 2022-04-22 17:19:23 -07:00
Elara6331 de19b77c13 Update module go version to 1.17 2022-04-22 17:15:41 -07:00
Elara6331 9990e92f19 Switch from custom socket API to rpcx 2022-04-22 17:12:30 -07:00
Elara6331 4534de7157 Fix typo in code (Czeck -> Czech) 2022-04-16 10:15:55 -07:00
Elara6331 d94d484484 Fix typo (Czeck -> Czech) 2022-04-16 10:14:18 -07:00
Elara6331 7309674dcf Use new changes in infinitime library to stop removing InfiniTime devices (Fixes #10) 2022-04-16 04:28:53 -07:00
Elara6331 c5ea3df255 Fix itctl panic when itd is not running (Fixes #14) 2022-04-02 15:20:31 -07:00
Elara6331 f7d4fc1b58 Merge pull request 'emoji translation: Add my frequently received emojis' (#15) from earboxer/itd:common-emojis into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/15
2022-03-25 17:22:02 -07:00
earboxer 0a3bacc3ee emoji translation: Add my frequently received emojis
mapped to a common ASCII emoticon, or to the shortcode
2022-03-21 11:58:13 -04:00
Elara6331 68aad1c0ed Remove debug code 2022-03-15 19:25:37 -07:00
Elara6331 5b87af872f Update 'cmd/itctl/main.go' 2022-03-15 16:16:44 -07:00
Elara6331 ffed644beb Remove exit error handler because it causes duplicated help text 2022-03-15 16:06:05 -07:00
Elara6331 b61baf5760 Transliterate song metadata (Fixes #13) 2022-03-11 13:14:23 -08:00
Elara6331 8d4e53b35f Merge pull request 'Romanian transliterate' (#12) from eugenr/itd:romanian into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/12
2022-03-11 10:04:26 -08:00
eugenr d56760b494 Add Romanian to README.md 2022-03-11 04:17:12 -08:00
eugenr 6369d46211 Romanian translit 2022-03-11 04:15:10 -08:00
Elara6331 597e7dab5f Make sure fs is only updated if dev.FS() succeeds (#11) 2022-03-08 08:32:31 -08:00
Elara6331 2112894889 Update infinitime library for #9 fix 2022-03-04 12:05:58 -08:00
Elara6331 02bf90f10e Rewrite itctl to use urfave/cli instead of spf13/cobra 2022-02-24 21:26:40 -08:00
Elara6331 65cae3aeab Add update weather command to itctl 2022-02-23 21:22:03 -08:00
Elara6331 dddc8780d6 Add default version.txt file 2022-02-22 08:44:50 -08:00
Elara6331 08dce2a110 Add version flag 2022-02-22 08:43:29 -08:00
Elara6331 b1d2fe6efb Add enable switch for weather to config 2022-02-22 08:33:27 -08:00
Elara6331 d83923a917 Add error logging for weather 2022-02-21 16:27:04 -08:00
Elara6331 032f735526 Implement weather via MET Norway 2022-02-21 16:18:52 -08:00
Elara6331 57768acb94 Switch from viper to koanf 2022-02-21 11:20:02 -08:00
Elara6331 953db860c2 Update version of infinitime library for rewritten connection code 2022-02-21 02:47:48 -08:00
Elara6331 78098b3833 Reorganize and clean code 2021-12-17 00:31:05 -08:00
Elara6331 3d89a03ca7 Update Infinitime library to use custom agent 2021-12-16 21:32:06 -08:00
Elara6331 dcec9f593b Propagate FS errors on read/write and close files when finished writing 2021-12-13 09:58:34 -08:00
Elara6331 233b0f77f0 Make paths absolute for firmware upgrades 2021-12-12 17:46:50 -08:00
Elara6331 8c020f792d Implement file transfer progress 2021-12-12 17:08:48 -08:00
Elara6331 56ed3dda88 Remove debug code 2021-12-11 22:23:01 -08:00
Elara6331 38eda50537 Create absolute directories for ITD to read/write 2021-12-11 22:13:21 -08:00
Elara6331 9bfdaef70c Directly read/write files from ITD 2021-12-11 22:11:01 -08:00
Elara6331 488b5a7d89 Fix comments in filesystem commands 2021-11-27 00:11:37 -08:00
Elara6331 9108dbd40b Fix and add error messages to fs operations 2021-11-27 00:03:13 -08:00
Elara6331 d8f4fd8aa5 Ensure that the FS works after a reconnect 2021-11-25 20:35:03 -08:00
Elara6331 371ffcc21f Remove replace directive 2021-11-25 19:49:07 -08:00
Elara6331 50db403614 Add missing responses to some FS operations 2021-11-25 19:46:04 -08:00
Elara6331 e88e8e487e Add newline for read file command if output is stdout 2021-11-25 19:44:43 -08:00
Elara6331 eda92e2d62 Get BLE FS once rather than on every connection 2021-11-25 19:41:44 -08:00
Elara6331 3eb93cdb35 Allow multiple call notification responses 2021-11-25 12:41:36 -08:00
Elara6331 d86216ef74 Remove playerctl from depencency list 2021-11-24 16:46:57 -08:00
Elara6331 9f055010c0 Update music control implementation 2021-11-24 16:44:36 -08:00
Elara6331 3e69b75a27 Remove useless function call 2021-11-24 13:07:48 -08:00
Elara6331 be99136bb5 Make sure modemmanager exists for call notifications 2021-11-24 13:04:20 -08:00
Elara6331 3a16bff83f Add comments 2021-11-24 12:00:44 -08:00
Elara6331 9bd78bccce Use clearer variable names 2021-11-24 11:54:16 -08:00
Elara6331 62817e4fbe Use new helper functions 2021-11-24 11:52:52 -08:00
Elara6331 715a53a91c Switch calls to use dbus library and add helpers for private connections 2021-11-24 11:36:36 -08:00
Elara6331 22709439c4 Switch to private bus connection 2021-11-23 22:03:41 -08:00
Elara6331 082fdc02f4 Add filesystem to itctl 2021-11-23 14:14:45 -08:00
Elara6331 49a7a51527 Allow multiple paths in mkdir and remove 2021-11-23 13:35:18 -08:00
Elara6331 0720feba96 Fix write file in api package 2021-11-23 11:19:21 -08:00
Elara6331 fcb5bcd967 Add BLE FS to API package 2021-11-23 11:12:16 -08:00
Elara6331 fe1ebc40a0 Implement BLE FS 2021-11-22 22:04:09 -08:00
Elara6331 8d6746e365 Update infinitime library to fix compatibility with BlueZ 5.62 2021-11-22 01:18:40 -08:00
Elara6331 e89e97749e Add reminder to validate firmware to itctl and itgui 2021-11-06 19:06:17 -07:00
Elara6331 ed3b7b2a99 Update default values to reflect new config fields 2021-11-01 11:28:55 -07:00
Elara6331 5cb6cfa5fb Upgrade infinitime library version 2021-11-01 11:21:37 -07:00
Elara6331 1c8fa559e4 Remove config version field 2021-10-27 08:34:10 -07:00
Elara6331 a3e9f1f4c4 Add whitelist support 2021-10-27 07:27:12 -07:00
Elara6331 2b75e64daf Add motion service to itgui 2021-10-25 09:45:19 -07:00
Elara6331 2a07dd3d1c Remove debug print and add error handling (itgui) 2021-10-25 00:11:41 -07:00
Elara6331 c881502b44 Use api package in itgui 2021-10-24 13:27:14 -07:00
Elara6331 c57c1f89b9 Add watch commands to itctl 2021-10-24 11:02:29 -07:00
Elara6331 5c8fd72f85 Handle unknown request type 2021-10-24 01:11:57 -07:00
Elara6331 cf7dbd0b9c Use request type for error response type 2021-10-24 01:09:27 -07:00
Elara6331 2426582bd3 Fix API package 2021-10-24 00:49:48 -07:00
Elara6331 8d29ece214 Return request type for response type 2021-10-24 00:45:50 -07:00
Elara6331 4b6b2a4581 Disable firmware updating error once progress channel closed 2021-10-23 22:03:33 -07:00
Elara6331 37c4fe5577 Use sent bytes to check if transfer complete 2021-10-23 19:36:23 -07:00
Elara6331 45621a98d5 Remove test 2021-10-23 18:42:22 -07:00
Elara6331 32cab6d00f Update itctl to use api 2021-10-23 18:41:03 -07:00
Elara6331 e45bfe3de8 Generalize socket cancellation and update API accordingly 2021-10-23 18:03:17 -07:00
Elara6331 2ab8d24a43 Reorganize itctl structure 2021-10-23 15:11:04 -07:00
Elara6331 d16c5ea96d Add cancellation to api package 2021-10-22 22:30:58 -07:00
Elara6331 46a891d852 Add responses to cancellation requests 2021-10-22 22:15:35 -07:00
Elara6331 ff8ce1b2a5 Add cancellation to watchable values 2021-10-22 22:14:01 -07:00
Elara6331 68bac8859f Add doc comments to api package 2021-10-22 21:01:18 -07:00
Elara6331 a235903583 Send response types in socket responses and create api package 2021-10-22 20:47:57 -07:00
Elara6331 b63960ed88 Update readme 2021-10-22 17:12:46 -07:00
Elara6331 869f487456 Add MotionValues type 2021-10-22 13:42:33 -07:00
Elara6331 86db246d4a Add motion service to itctl 2021-10-22 13:40:16 -07:00
Elara6331 4b95af905d Implement motion service 2021-10-22 13:21:14 -07:00
Elara6331 0054a0b3cf Update infinitime library version to fix intermittent DFU issues 2021-10-21 20:27:58 -07:00
Elara6331 0024a9fe69 Remove replace directive 2021-10-15 00:27:10 -07:00
Elara6331 ba443d3836 Mention call notifications in readme 2021-10-15 00:26:14 -07:00
Elara6331 a5f552fe8f Add call notifications for ModemManager 2021-10-15 00:25:34 -07:00
Elara6331 ce31929a73 Show update file names when selected in itgui 2021-10-07 13:38:13 -07:00
Elara6331 b5803873ce Remove replace directive 2021-10-06 17:47:07 -07:00
Elara6331 85699e4999 Update infinitime library 2021-10-06 17:15:42 -07:00
Elara6331 47edf7773f Add and fix comments, fix transliteration maps so only first character is capitalized 2021-10-06 13:26:16 -07:00
Elara6331 e70c9f4d7c Fix chinese transliteration when chinese characters are not followed by non-chinese characters 2021-10-06 13:15:49 -07:00
Elara6331 a4598269e5 Only do init once for Armenian transliteration 2021-10-06 13:08:25 -07:00
Elara6331 fe4b0ec203 Add init functions to transliterators 2021-10-06 09:41:33 -07:00
Elara6331 3274eb6acf Fix capital letters for Armenian transliteration 2021-10-05 09:09:19 -07:00
Elara6331 8019a3521e Add Chinese transliteration via Pinyin conversion library 2021-10-04 22:26:16 -07:00
Elara6331 b365fdfcd3 Update variable names and comments for interface-based transliteration 2021-10-04 20:23:54 -07:00
Elara6331 a3281a7e15 Fix Korean transliteration 2021-10-04 20:06:08 -07:00
Elara6331 12c5e924a2 Add korean transliteration 2021-10-04 19:07:54 -07:00
Elara6331 91f2f28076 Use interface to allow for more complex transliteration implementations 2021-10-04 17:45:26 -07:00
Elara6331 471de06158 Fix German transliteration for Ü and add attribution 2021-10-04 13:17:48 -07:00
Elara6331 ca02e8c62f Add transliteration 2021-10-04 01:05:01 -07:00
Elara6331 9b2507de4c Break transfer loops after refreshing progress bar 2021-08-27 09:01:46 -07:00
Elara6331 5bc63b7864 Add fatal error dialog 2021-08-27 08:47:24 -07:00
Elara6331 316e113e5d Mention GUI in README 2021-08-26 09:01:03 -07:00
Elara6331 50f3f244a3 Add comments to gui 2021-08-26 08:47:17 -07:00
Elara6331 ea63f43638 Add GUI frontend 2021-08-25 21:18:24 -07:00
Elara6331 9574f3dd36 Fix indentation in config 2021-08-24 20:35:25 -07:00
Elara6331 36b683204d Switch to iota for request types and move to types package 2021-08-24 20:32:17 -07:00
Elara6331 0fe83fccc1 Fix debug config paths 2021-08-24 08:55:22 -07:00
Elara6331 ef6c37c20b Add config defaults and run go fmt 2021-08-24 08:54:08 -07:00
Elara6331 59ecd11340 Create new config format 2021-08-24 08:33:41 -07:00
Elara6331 7b5c228591 Remove replace directive 2021-08-22 15:07:45 -07:00
Elara6331 eed4a41230 Fix find and replace error 2021-08-22 13:53:32 -07:00
Elara6331 c1134926aa Update infinitime library to fix connection bug 2021-08-22 13:13:37 -07:00
Elara6331 c0bbfff872 Use new pair timeout option 2021-08-21 20:35:21 -07:00
Elara6331 4308789af5 Mention interactive mode in readme 2021-08-21 19:07:59 -07:00
Elara6331 76c54339cf Disable completion command 2021-08-21 19:03:18 -07:00
Elara6331 688f5e5004 Fix binary download link 2021-08-21 18:52:11 -07:00
Elara6331 f4b2a21bd8 Add badges 2021-08-21 18:48:43 -07:00
Elara6331 a769bac3c2 Add uninstall rule to makefile 2021-08-21 17:17:25 -07:00
Elara6331 3dfa572eb3 Add CI status to readme 2021-08-21 16:36:10 -07:00
Elara6331 ff4a62bc0b Add GOFLAGS environment variable to makefile 2021-08-21 16:03:54 -07:00
Elara6331 a2ab8ad4f3 Add gitm for mirroring 2021-08-21 15:59:19 -07:00
Elara6331 b48b774e97 Specify minimum go version in readme 2021-08-21 15:35:36 -07:00
Elara6331 ce505280ec Add starting to readme 2021-08-21 15:14:37 -07:00
Elara6331 70dde9554d Change recoverable errors to warn log level to stop shell from exiting 2021-08-21 14:15:55 -07:00
Elara6331 bf1cda2e7e Add interactive mode to itctl 2021-08-21 12:30:16 -07:00
Elara6331 43ea68e62f Prioritize config in home directory over /etc 2021-08-21 10:26:12 -07:00
Elara6331 b68e81cf8d Mention getting info from watch in readme 2021-08-21 09:40:29 -07:00
Elara6331 763f11e191 Mention automatic config updates in readme 2021-08-21 09:37:20 -07:00
Elara6331 031295ad9a Watch config for changes and apply automatically 2021-08-21 03:07:48 -07:00
Elara6331 cf1794215e Remove replace directive and update infinitime library 2021-08-21 01:37:16 -07:00
Elara6331 203224ed4a Initial Commit 2021-08-21 01:19:49 -07:00
56 changed files with 3257 additions and 811 deletions
+1
View File
@@ -0,0 +1 @@
liberapay: Elara6331
+9 -7
View File
@@ -14,6 +14,7 @@ builds:
- amd64 - amd64
- arm - arm
- arm64 - arm64
- riscv64
goarm: goarm:
- 7 - 7
- id: itctl - id: itctl
@@ -43,6 +44,7 @@ archives:
- README.md - README.md
- itd.toml - itd.toml
- itd.service - itd.service
allow_different_binary_count: true
nfpms: nfpms:
- id: itd - id: itd
file_name_template: >- file_name_template: >-
@@ -53,8 +55,8 @@ nfpms:
{{- else }}{{.Arch}} {{- else }}{{.Arch}}
{{- end }} {{- end }}
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch" description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd' homepage: 'https://gitea.elara.ws/Elara6331/itd'
maintainer: 'Arsen Musyaelyan <arsen@arsenm.dev>' maintainer: 'Elara Ivy <elara@elara.ws>'
license: GPLv3 license: GPLv3
formats: formats:
- apk - apk
@@ -74,10 +76,10 @@ nfpms:
mode: 0755 mode: 0755
aurs: aurs:
- name: itd-bin - name: itd-bin
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd' homepage: 'https://gitea.elara.ws/Elara6331/itd'
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch" description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
maintainers: maintainers:
- 'Arsen Musyaelyan <arsen@arsenm.dev>' - 'Elara Ivy <elara@elara.ws>'
license: GPLv3 license: GPLv3
private_key: '{{ .Env.AUR_KEY }}' private_key: '{{ .Env.AUR_KEY }}'
git_url: 'ssh://aur@aur.archlinux.org/itd-bin.git' git_url: 'ssh://aur@aur.archlinux.org/itd-bin.git'
@@ -105,11 +107,11 @@ aurs:
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/itd/LICENSE" install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/itd/LICENSE"
release: release:
gitea: gitea:
owner: Arsen6331 owner: Elara6331
name: itd name: itd
gitea_urls: gitea_urls:
api: 'https://gitea.arsenm.dev/api/v1/' api: 'https://gitea.elara.ws/api/v1/'
download: 'https://gitea.arsenm.dev' download: 'https://gitea.elara.ws'
skip_tls_verify: false skip_tls_verify: false
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
+14 -18
View File
@@ -1,11 +1,17 @@
# ITD # ITD
## InfiniTime Daemon ## InfiniTime Daemon
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io). `itd` is a daemon that uses my infinitime [library](https://go.elara.ws/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
[![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/itd/status.svg)](https://ci.arsenm.dev/Arsen6331/itd) [![status-badge](https://ci.elara.ws/api/badges/Elara6331/itd/status.svg)](https://ci.elara.ws/Elara6331/itd)
[![itd-git AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/) [![itd-git AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/)
[![itd-bin AUR package](https://img.shields.io/aur/version/itd-bin?label=itd-bin&logo=archlinux)](https://aur.archlinux.org/packages/itd-bin/) [![itd-bin AUR package](https://img.shields.io/aur/version/itd-bin?label=itd-bin&logo=archlinux)](https://aur.archlinux.org/packages/itd-bin/)
[![LURE badge for itd-git](https://lure.sh/pkg/default/itd-git/badge.svg)](https://lure.sh/pkg/default/itd-git)
[![LURE badge for itd-bin](https://lure.sh/pkg/default/itd-bin/badge.svg)](https://lure.sh/pkg/default/itd-bin)
This repository is part of the Software Heritage Archive:
[![SWH](https://archive.softwareheritage.org/badge/swh:1:dir:1374aa47b5c0a0d636d6f9c69f77af5e5bae99b2/)](https://archive.softwareheritage.org/swh:1:dir:1374aa47b5c0a0d636d6f9c69f77af5e5bae99b2;origin=https://gitea.elara.ws/Elara6331/itd;visit=swh:1:snp:d2935acbc966dfe1b15c771927bb08b5fc2ec89f;anchor=swh:1:rev:395cded9758dccc020fcd5b666f83a62308c9ab7)
--- ---
@@ -22,6 +28,7 @@
- Weather - Weather
- BLE Filesystem - BLE Filesystem
- Navigation (PureMaps) - Navigation (PureMaps)
- FUSE Filesystem
--- ---
@@ -35,19 +42,19 @@ Use the `itd-bin` or `itd-git` AUR packages.
#### Debian/Ubuntu #### Debian/Ubuntu
- Go to the [latest release](https://gitea.arsenm.dev/Arsen6331/itd/releases/latest) and download the `.deb` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal. - Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.deb` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
- Run `sudo apt install <package>`, replacing `<package>` with the path to the downloaded file. Note: relative paths must begin with `./`. - Run `sudo apt install <package>`, replacing `<package>` with the path to the downloaded file. Note: relative paths must begin with `./`.
- Example: `sudo apt install ~/Downloads/itd-0.0.7-linux-aarch64.deb` - Example: `sudo apt install ~/Downloads/itd-0.0.7-linux-aarch64.deb`
#### Fedora #### Fedora
- Go to the [latest release](https://gitea.arsenm.dev/Arsen6331/itd/releases/latest) and download the `.rpm` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal. - Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.rpm` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
- Run `sudo dnf install <package>`, replacing `<package>` with the path to the downloaded file. - Run `sudo dnf install <package>`, replacing `<package>` with the path to the downloaded file.
- Example: `sudo dnf install ~/Downloads/itd-0.0.7-linux-aarch64.rpm` - Example: `sudo dnf install ~/Downloads/itd-0.0.7-linux-aarch64.rpm`
#### Alpine (and postmarketOS) #### Alpine (and postmarketOS)
- Go to the [latest release](https://gitea.arsenm.dev/Arsen6331/itd/releases/latest) and download the `.apk` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal. - Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.apk` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
- Run `sudo apk add --allow-untrusted <package>`, replacing `<package>` with the path to the downloaded file. - Run `sudo apk add --allow-untrusted <package>`, replacing `<package>` with the path to the downloaded file.
- Example: `sudo apk add --allow-untrusted ~/Downloads/itd-0.0.7-linux-aarch64.apk` - Example: `sudo apk add --allow-untrusted ~/Downloads/itd-0.0.7-linux-aarch64.apk`
@@ -90,11 +97,11 @@ In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [Fyn
#### Easy Installation #### Easy Installation
The easiest way to install `itgui` is to use my other project, [LURE](https://gitea.arsenm.dev/Arsen6331/lure). LURE will only work if your package manager is `apt`, `dnf`, `yum`, `zypper`, `pacman`, or `apk`. The easiest way to install `itgui` is to use my other project, [LURE](https://gitea.elara.ws/Elara6331/lure). LURE will only work if your package manager is `apt`, `dnf`, `yum`, `zypper`, `pacman`, or `apk`.
Instructions: Instructions:
1. Install LURE. This can be done with the following command: `curl https://www.arsenm.dev/lure.sh | bash`. 1. Install LURE. This can be done with the following command: `curl https://www.elara.ws/lure.sh | bash`.
2. Check to make sure LURE is properly installed by running `lure ref`. 2. Check to make sure LURE is properly installed by running `lure ref`.
3. Run `lure in itgui`. This process may take a while as it will compile `itgui` from source and package it for your distro. 3. Run `lure in itgui`. This process may take a while as it will compile `itgui` from source and package it for your distro.
4. Once the process is complete, you should be able to open and use `itgui` like any other app. 4. Once the process is complete, you should be able to open and use `itgui` like any other app.
@@ -137,17 +144,6 @@ Due to the use of OpenGL, cross-compilation of `itgui` isn't as simple as that o
--- ---
### Installation
To install, install the go compiler and make. Usually, go is provided by a package either named `go` or `golang`, and make is usually provided by `make`. The go compiler must be version 1.17 or newer for various new `reflect` features.
To install, run
```shell
make && sudo make install
```
---
### Socket ### Socket
This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directly control the daemon and, by extension, the connected watch. This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directly control the daemon and, by extension, the connected watch.
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"io" "io"
"net" "net"
"go.arsenm.dev/drpc/muxconn" "go.elara.ws/drpc/muxconn"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
"storj.io/drpc" "storj.io/drpc"
) )
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
type DFUProgress struct { type DFUProgress struct {
+5 -2
View File
@@ -5,7 +5,7 @@ import (
"errors" "errors"
"io" "io"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
type FSClient struct { type FSClient struct {
@@ -42,7 +42,10 @@ func (c *FSClient) Mkdir(ctx context.Context, paths ...string) error {
func (c *FSClient) ReadDir(ctx context.Context, dir string) ([]FileInfo, error) { func (c *FSClient) ReadDir(ctx context.Context, dir string) ([]FileInfo, error) {
res, err := c.client.ReadDir(ctx, &rpc.PathRequest{Path: dir}) res, err := c.client.ReadDir(ctx, &rpc.PathRequest{Path: dir})
return convertEntries(res.Entries), err if err != nil {
return nil, err
}
return convertEntries(res.Entries), nil
} }
func convertEntries(e []*rpc.FileInfo) []FileInfo { func convertEntries(e []*rpc.FileInfo) []FileInfo {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
func (c *Client) HeartRate(ctx context.Context) (uint8, error) { func (c *Client) HeartRate(ctx context.Context) (uint8, error) {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
func (c *Client) Notify(ctx context.Context, title, body string) error { func (c *Client) Notify(ctx context.Context, title, body string) error {
+5 -5
View File
@@ -3,15 +3,15 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
type ResourceOperation uint8 type ResourceOperation infinitime.ResourceOperation
const ( const (
ResourceOperationRemoveObsolete = infinitime.ResourceOperationRemoveObsolete ResourceRemove = infinitime.ResourceRemove
ResourceOperationUpload = infinitime.ResourceOperationUpload ResourceUpload = infinitime.ResourceUpload
) )
type ResourceLoadProgress struct { type ResourceLoadProgress struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
func (c *Client) SetTime(ctx context.Context, t time.Time) error { func (c *Client) SetTime(ctx context.Context, t time.Time) error {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
func (c *Client) WeatherUpdate(ctx context.Context) error { func (c *Client) WeatherUpdate(ctx context.Context) error {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import ( import (
"context" "context"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/itd/internal/rpc"
) )
func (c *Client) WatchHeartRate(ctx context.Context) (<-chan uint8, error) { func (c *Client) WatchHeartRate(ctx context.Context) (<-chan uint8, error) {
+44 -15
View File
@@ -2,15 +2,14 @@ package main
import ( import (
"context" "context"
"sync"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/itd/internal/utils" "go.elara.ws/itd/internal/utils"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
func initCallNotifs(ctx context.Context, dev *infinitime.Device) error { func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// Connect to system bus. This connection is for method calls. // Connect to system bus. This connection is for method calls.
conn, err := utils.NewSystemBusConn(ctx) conn, err := utils.NewSystemBusConn(ctx)
if err != nil { if err != nil {
@@ -50,12 +49,14 @@ func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
// Notify channel upon received message // Notify channel upon received message
monitorConn.Eavesdrop(callCh) monitorConn.Eavesdrop(callCh)
var respHandlerOnce sync.Once
var callObj dbus.BusObject var callObj dbus.BusObject
wg.Add(1)
go func() { go func() {
// For every message received defer wg.Done("callNotifs")
for event := range callCh { for {
select {
case event := <-callCh:
// Get path to call object // Get path to call object
callPath := event.Body[0].(dbus.ObjectPath) callPath := event.Body[0].(dbus.ObjectPath)
// Get call object // Get call object
@@ -68,16 +69,20 @@ func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
continue continue
} }
// Send call notification to InfiniTime // Get direction of call object using method call connection
resCh, err := dev.NotifyCall(phoneNum) direction, err := getDirection(conn, callObj)
if err != nil { if err != nil {
log.Error("Error getting call direction").Err(err).Send()
continue continue
} }
go respHandlerOnce.Do(func() { if direction != MMCallDirectionIncoming {
// Wait for PineTime response continue
for res := range resCh { }
switch res {
// Send call notification to InfiniTime
err = dev.NotifyCall(phoneNum, func(cs infinitime.CallStatus) {
switch cs {
case infinitime.CallStatusAccepted: case infinitime.CallStatusAccepted:
// Attempt to accept call // Attempt to accept call
err = acceptCall(ctx, conn, callObj) err = acceptCall(ctx, conn, callObj)
@@ -94,8 +99,13 @@ func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
// Warn about unimplemented muting // Warn about unimplemented muting
log.Warn("Muting calls is not implemented").Send() log.Warn("Muting calls is not implemented").Send()
} }
}
}) })
if err != nil {
continue
}
case <-ctx.Done():
return
}
} }
}() }()
@@ -125,6 +135,25 @@ func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
return out, nil return out, nil
} }
type MMCallDirection int
const (
MMCallDirectionUnknown MMCallDirection = iota
MMCallDirectionIncoming
MMCallDirectionOutgoing
)
// getDirection gets the direction of a call object using a DBus connection
func getDirection(conn *dbus.Conn, callObj dbus.BusObject) (MMCallDirection, error) {
var out MMCallDirection
// Get number property on DBus object and store return value in out
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Direction", &out)
if err != nil {
return 0, err
}
return out, nil
}
// getPhoneNum accepts a call using a DBus connection // getPhoneNum accepts a call using a DBus connection
func acceptCall(ctx context.Context, conn *dbus.Conn, callObj dbus.BusObject) error { func acceptCall(ctx context.Context, conn *dbus.Conn, callObj dbus.BusObject) error {
// Call Accept() method on DBus object // Call Accept() method on DBus object
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/cheggaaa/pb/v3" "github.com/cheggaaa/pb/v3"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
func fwUpgrade(c *cli.Context) error { func fwUpgrade(c *cli.Context) error {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"time" "time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
"go.arsenm.dev/logger" "go.elara.ws/logger"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
var client *api.Client var client *api.Client
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/cheggaaa/pb/v3" "github.com/cheggaaa/pb/v3"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
) )
func resourcesLoad(c *cli.Context) error { func resourcesLoad(c *cli.Context) error {
@@ -39,7 +39,7 @@ func resLoad(ctx context.Context, args []string) error {
return evt.Err return evt.Err
} }
if evt.Operation == infinitime.ResourceOperationRemoveObsolete { if evt.Operation == infinitime.ResourceRemove {
bar.SetTemplateString(rmTmpl) bar.SetTemplateString(rmTmpl)
bar.Set("filename", evt.Name) bar.Set("filename", evt.Name)
} else { } else {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func firmwareTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { func firmwareTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+4 -4
View File
@@ -11,8 +11,8 @@ import (
"fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/api"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/infinitime"
) )
func fsTab(ctx context.Context, client *api.Client, w fyne.Window, opened chan struct{}) fyne.CanvasObject { func fsTab(ctx context.Context, client *api.Client, w fyne.Window, opened chan struct{}) fyne.CanvasObject {
@@ -77,9 +77,9 @@ func fsTab(ctx context.Context, client *api.Client, w fyne.Window, opened chan s
for evt := range progCh { for evt := range progCh {
switch evt.Operation { switch evt.Operation {
case infinitime.ResourceOperationRemoveObsolete: case infinitime.ResourceRemove:
progressDlg.SetText("Removing " + evt.Name) progressDlg.SetText("Removing " + evt.Name)
case infinitime.ResourceOperationUpload: case infinitime.ResourceUpload:
progressDlg.SetText("Uploading " + evt.Name) progressDlg.SetText("Uploading " + evt.Name)
progressDlg.SetTotal(float64(evt.Total)) progressDlg.SetTotal(float64(evt.Total))
progressDlg.SetValue(float64(evt.Sent)) progressDlg.SetValue(float64(evt.Sent))
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/x/fyne/widget/charts" "fyne.io/x/fyne/widget/charts"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func infoTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { func infoTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func main() { func main() {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func motionTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { func motionTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func notifyTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { func notifyTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/api" "go.elara.ws/itd/api"
) )
func timeTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject { func timeTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/file"
"go.arsenm.dev/logger" "go.elara.ws/logger"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
var cfgDir string var cfgDir string
+22 -8
View File
@@ -1,17 +1,26 @@
package main package main
import ( import (
"go.arsenm.dev/itd/internal/fusefs" "context"
"os" "os"
"github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"go.arsenm.dev/logger/log" "go.elara.ws/itd/infinitime"
"context" "go.elara.ws/itd/internal/fusefs"
"go.arsenm.dev/infinitime" "go.elara.ws/logger/log"
) )
func startFUSE(ctx context.Context, dev *infinitime.Device) error { func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// This is where we'll mount the FS // This is where we'll mount the FS
os.Mkdir(k.String("fuse.mountpoint"), 0755) err := os.MkdirAll(k.String("fuse.mountpoint"), 0o755)
if err != nil && !os.IsExist(err) {
return err
}
// Ignore the error because nothing might be mounted on the mountpoint
_ = fusefs.Unmount(k.String("fuse.mountpoint"))
root, err := fusefs.BuildRootNode(dev) root, err := fusefs.BuildRootNode(dev)
if err != nil { if err != nil {
log.Error("Building root node failed"). log.Error("Building root node failed").
@@ -46,7 +55,12 @@ func startFUSE(ctx context.Context, dev *infinitime.Device) error {
return err return err
} }
// Wait until unmount before exiting wg.Add(1)
go server.Serve() go func() {
defer wg.Done("fuse")
<-ctx.Done()
server.Unmount()
}()
return nil return nil
} }
+12 -11
View File
@@ -1,26 +1,29 @@
module go.arsenm.dev/itd module go.elara.ws/itd
go 1.18 go 1.18
replace fyne.io/x/fyne => github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb replace fyne.io/x/fyne => github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb
replace tinygo.org/x/bluetooth => github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768
require ( require (
fyne.io/fyne/v2 v2.3.0 fyne.io/fyne/v2 v2.3.0
fyne.io/x/fyne v0.0.0-20220107050838-c4a1de51d4ce fyne.io/x/fyne v0.0.0-20220107050838-c4a1de51d4ce
github.com/cheggaaa/pb/v3 v3.1.0 github.com/cheggaaa/pb/v3 v3.1.0
github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d
github.com/godbus/dbus/v5 v5.1.0 github.com/godbus/dbus/v5 v5.1.0
github.com/hanwen/go-fuse/v2 v2.2.0
github.com/knadh/koanf v1.4.4 github.com/knadh/koanf v1.4.4
github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-isatty v0.0.17
github.com/mozillazg/go-pinyin v0.19.0 github.com/mozillazg/go-pinyin v0.19.0
github.com/urfave/cli/v2 v2.23.7 github.com/urfave/cli/v2 v2.23.7
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98 go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469 go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df
golang.org/x/text v0.5.0 golang.org/x/text v0.5.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
modernc.org/sqlite v1.20.1 modernc.org/sqlite v1.20.1
storj.io/drpc v0.0.32 storj.io/drpc v0.0.32
tinygo.org/x/bluetooth v0.9.0
) )
require ( require (
@@ -31,21 +34,19 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/fredbi/uri v1.0.0 // indirect github.com/fredbi/uri v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 // indirect github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 // indirect
github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect
github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 // indirect github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 // indirect
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // indirect github.com/gookit/color v1.5.1 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/hanwen/go-fuse/v2 v2.2.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
@@ -54,18 +55,18 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/rivo/uniseg v0.4.3 // indirect github.com/rivo/uniseg v0.4.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.8.1 // indirect github.com/stretchr/testify v1.8.1 // indirect
github.com/tevino/abool v1.2.0 // indirect github.com/tevino/abool v1.2.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/tinygo-org/cbgo v0.0.4 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/goldmark v1.5.3 // indirect github.com/yuin/goldmark v1.5.3 // indirect
@@ -74,7 +75,7 @@ require (
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect
golang.org/x/mod v0.7.0 // indirect golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.11.0 // indirect
golang.org/x/tools v0.4.0 // indirect golang.org/x/tools v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect
+18 -25
View File
@@ -107,6 +107,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768 h1:iWP52WinMhd+pQB+2GedWvUxkd4pMqFvV0S6MjMFQSc=
github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768/go.mod h1:V9XwH/xQ2SmCIW+T0pmpL7VzijY53JRVsJcDM0YN6PI=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -119,7 +121,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
@@ -129,8 +130,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU= github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU=
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
@@ -161,15 +160,14 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 h1:J6XG/Xx7uCCpskM71R6YAgPHd/E8FzhyPhL6Ll94uMY= github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 h1:J6XG/Xx7uCCpskM71R6YAgPHd/E8FzhyPhL6Ll94uMY=
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -240,7 +238,6 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -341,6 +338,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -396,8 +395,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c= github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 h1:kOnq7TfaAO2Vc/MHxPqFIXe00y1qBxJAvhctXdko6vo=
github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -409,7 +406,6 @@ github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnu
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@@ -454,6 +450,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4 h1:zurEWtOr/OYiTb5bcD7eeHLOfj6vCR30uldlwse1cSM=
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
@@ -461,10 +459,11 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -499,17 +498,16 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY= github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -527,12 +525,10 @@ github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5ta
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98 h1:Tt/05IEOeIEfovj8YLxDaZz+SP8eR1uPLtcgD5Pf4EI= go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d h1:ANb8YPtcxPipwKgmnW688e5PGpNaLh+22nO2LBpIPOU=
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98/go.mod h1:K5cFls42m5q1RIphTVojRdXLaoCknq/kBqQt8Ow3XuA= go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d/go.mod h1:NDprjiVqKXQKVGzX7jp2g/jctsUbvOxz1nN15QOBEGk=
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469 h1:LsJHg+8rQSYnTE1sSCjBCACxUUVMZIOQani8J6wF2/E= go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae h1:d+gJUhEWSrOjrrfgeydYWEr8TTnx0DLvcVhghaOsFeE=
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469/go.mod h1:scUyDmLmCHn6CanGbau8yjTjzyhUbLJcsjmDCCKMIII= go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df h1:8mBHvEe7BJmpOeKSMA5YLqrGo9dCpePocTeR0C1+/2w=
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@@ -724,7 +720,6 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -746,7 +741,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -754,8 +748,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -818,7 +812,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+142
View File
@@ -0,0 +1,142 @@
package infinitime
import "tinygo.org/x/bluetooth"
type btChar struct {
Name string
ID bluetooth.UUID
ServiceID bluetooth.UUID
}
var (
musicServiceUUID = mustParse("00000000-78fc-48fe-8e23-433b3a1942d0")
navigationServiceUUID = mustParse("00010000-78fc-48fe-8e23-433b3a1942d0")
motionServiceUUID = mustParse("00030000-78fc-48fe-8e23-433b3a1942d0")
weatherServiceUUID = mustParse("00050000-78fc-48fe-8e23-433b3a1942d0")
)
var (
newAlertChar = btChar{
"New Alert",
bluetooth.CharacteristicUUIDNewAlert,
bluetooth.ServiceUUIDAlertNotification,
}
notifEventChar = btChar{
"Notification Event",
mustParse("00020001-78fc-48fe-8e23-433b3a1942d0"),
bluetooth.ServiceUUIDAlertNotification,
}
stepCountChar = btChar{
"Step Count",
mustParse("00030001-78fc-48fe-8e23-433b3a1942d0"),
motionServiceUUID,
}
rawMotionChar = btChar{
"Raw Motion",
mustParse("00030002-78fc-48fe-8e23-433b3a1942d0"),
motionServiceUUID,
}
firmwareVerChar = btChar{
"Firmware Version",
bluetooth.CharacteristicUUIDFirmwareRevisionString,
bluetooth.ServiceUUIDDeviceInformation,
}
currentTimeChar = btChar{
"Current Time",
bluetooth.CharacteristicUUIDCurrentTime,
bluetooth.ServiceUUIDCurrentTime,
}
localTimeChar = btChar{
"Local Time",
bluetooth.CharacteristicUUIDLocalTimeInformation,
bluetooth.ServiceUUIDCurrentTime,
}
batteryLevelChar = btChar{
"Battery Level",
bluetooth.CharacteristicUUIDBatteryLevel,
bluetooth.ServiceUUIDBattery,
}
heartRateChar = btChar{
"Heart Rate",
bluetooth.CharacteristicUUIDHeartRateMeasurement,
bluetooth.ServiceUUIDHeartRate,
}
fsVersionChar = btChar{
"Filesystem Version",
mustParse("adaf0200-4669-6c65-5472-616e73666572"),
bluetooth.ServiceUUIDFileTransferByAdafruit,
}
fsTransferChar = btChar{
"Filesystem Transfer",
mustParse("adaf0200-4669-6c65-5472-616e73666572"),
bluetooth.ServiceUUIDFileTransferByAdafruit,
}
dfuCtrlPointChar = btChar{
"DFU Control Point",
bluetooth.CharacteristicUUIDLegacyDFUControlPoint,
bluetooth.ServiceUUIDLegacyDFU,
}
dfuPacketChar = btChar{
"DFU Packet",
bluetooth.CharacteristicUUIDLegacyDFUPacket,
bluetooth.ServiceUUIDLegacyDFU,
}
navigationFlagsChar = btChar{
"Navigation Flags",
mustParse("00010001-78fc-48fe-8e23-433b3a1942d0"),
navigationServiceUUID,
}
navigationNarrativeChar = btChar{
"Navigation Narrative",
mustParse("00010002-78fc-48fe-8e23-433b3a1942d0"),
navigationServiceUUID,
}
navigationManDist = btChar{
"Navigation Man Dist",
mustParse("00010003-78fc-48fe-8e23-433b3a1942d0"),
navigationServiceUUID,
}
navigationProgress = btChar{
"Navigation Progress",
mustParse("00010004-78fc-48fe-8e23-433b3a1942d0"),
navigationServiceUUID,
}
weatherDataChar = btChar{
"Weather Data",
mustParse("00050001-78fc-48fe-8e23-433b3a1942d0"),
weatherServiceUUID,
}
musicEventChar = btChar{
"Music Event",
mustParse("00000001-78fc-48fe-8e23-433b3a1942d0"),
musicServiceUUID,
}
musicStatusChar = btChar{
"Music Status",
mustParse("00000002-78fc-48fe-8e23-433b3a1942d0"),
musicServiceUUID,
}
musicArtistChar = btChar{
"Music Artist",
mustParse("00000003-78fc-48fe-8e23-433b3a1942d0"),
musicServiceUUID,
}
musicTrackChar = btChar{
"Music Track",
mustParse("00000004-78fc-48fe-8e23-433b3a1942d0"),
musicServiceUUID,
}
musicAlbumChar = btChar{
"Music Album",
mustParse("00000005-78fc-48fe-8e23-433b3a1942d0"),
musicServiceUUID,
}
)
func mustParse(s string) bluetooth.UUID {
uuid, err := bluetooth.ParseUUID(s)
if err != nil {
panic(err)
}
return uuid
}
+222
View File
@@ -0,0 +1,222 @@
package infinitime
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/fs"
"tinygo.org/x/bluetooth"
)
const (
dfuSegmentSize = 20 // Size of each firmware packet
dfuPktRecvInterval = 10 // Amount of packets to send before checking for receipt
)
var (
dfuCmdStart = []byte{0x01, 0x04}
dfuCmdRecvInitPkt = []byte{0x02, 0x00}
dfuCmdInitPktComplete = []byte{0x02, 0x01}
dfuCmdPktReceiptInterval = []byte{0x08}
dfuCmdRecvFirmware = []byte{0x03}
dfuCmdValidate = []byte{0x04}
dfuCmdActivateReset = []byte{0x05}
dfuResponseStart = []byte{0x10, 0x01, 0x01}
dfuResponseInitParams = []byte{0x10, 0x02, 0x01}
dfuResponseRecvFwImgSuccess = []byte{0x10, 0x03, 0x01}
dfuResponseValidate = []byte{0x10, 0x04, 0x01}
)
// DFUOptions contains options for [UpgradeFirmware]
type DFUOptions struct {
InitPacket fs.File
FirmwareImage fs.File
ProgressFunc func(sent, received, total uint32)
SegmentSize int
ReceiveInterval uint8
}
// UpgradeFirmware upgrades the firmware running on the PineTime.
func (d *Device) UpgradeFirmware(opts DFUOptions) error {
if opts.SegmentSize <= 0 {
opts.SegmentSize = dfuSegmentSize
}
if opts.ReceiveInterval <= 0 {
opts.ReceiveInterval = dfuPktRecvInterval
}
ctrlPoint, err := d.getChar(dfuCtrlPointChar)
if err != nil {
return err
}
packet, err := d.getChar(dfuPacketChar)
if err != nil {
return err
}
d.deviceMtx.Lock()
defer d.deviceMtx.Unlock()
d.updating.Store(true)
defer d.updating.Store(false)
_, err = ctrlPoint.WriteWithoutResponse(dfuCmdStart)
if err != nil {
return err
}
fi, err := opts.FirmwareImage.Stat()
if err != nil {
return err
}
size := uint32(fi.Size())
sizePacket := make([]byte, 8, 12)
sizePacket = binary.LittleEndian.AppendUint32(sizePacket, size)
_, err = packet.WriteWithoutResponse(sizePacket)
if err != nil {
return err
}
_, err = awaitDFUResponse(ctrlPoint, dfuResponseStart)
if err != nil {
return err
}
err = writeDFUInitPacket(ctrlPoint, packet, opts.InitPacket)
if err != nil {
return err
}
err = setRecvInterval(ctrlPoint, opts.ReceiveInterval)
if err != nil {
return err
}
err = sendFirmware(ctrlPoint, packet, opts, size)
if err != nil {
return err
}
return finalize(ctrlPoint)
}
func finalize(ctrlPoint *bluetooth.DeviceCharacteristic) error {
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdValidate)
if err != nil {
return err
}
_, err = awaitDFUResponse(ctrlPoint, dfuResponseValidate)
if err != nil {
return err
}
_, _ = ctrlPoint.WriteWithoutResponse(dfuCmdActivateReset)
return nil
}
func sendFirmware(ctrlPoint, packet *bluetooth.DeviceCharacteristic, opts DFUOptions, totalSize uint32) error {
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdRecvFirmware)
if err != nil {
return err
}
var (
chunksSinceReceipt uint8
bytesSent uint32
)
chunk := make([]byte, opts.SegmentSize)
for {
n, err := opts.FirmwareImage.Read(chunk)
if err != nil && !errors.Is(err, io.EOF) {
return err
} else if n == 0 {
break
}
bytesSent += uint32(n)
_, err = packet.WriteWithoutResponse(chunk[:n])
if err != nil {
return err
}
if errors.Is(err, io.EOF) {
break
}
chunksSinceReceipt += 1
if chunksSinceReceipt == opts.ReceiveInterval {
sizeData, err := awaitDFUResponse(ctrlPoint, []byte{0x11})
if err != nil {
return err
}
size := binary.LittleEndian.Uint32(sizeData)
if size != bytesSent {
return fmt.Errorf("size mismatch: expected %d, got %d", bytesSent, size)
}
if opts.ProgressFunc != nil {
opts.ProgressFunc(bytesSent, size, totalSize)
}
chunksSinceReceipt = 0
}
}
return nil
}
func writeDFUInitPacket(ctrlPoint, packet *bluetooth.DeviceCharacteristic, initPkt fs.File) error {
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdRecvInitPkt)
if err != nil {
return err
}
initData, err := io.ReadAll(initPkt)
if err != nil {
return err
}
_, err = packet.WriteWithoutResponse(initData)
if err != nil {
return err
}
_, err = ctrlPoint.WriteWithoutResponse(dfuCmdInitPktComplete)
if err != nil {
return err
}
_, err = awaitDFUResponse(ctrlPoint, dfuResponseInitParams)
return err
}
func setRecvInterval(ctrlPoint *bluetooth.DeviceCharacteristic, interval uint8) error {
_, err := ctrlPoint.WriteWithoutResponse(append(dfuCmdPktReceiptInterval, interval))
return err
}
func awaitDFUResponse(ctrlPoint *bluetooth.DeviceCharacteristic, expect []byte) ([]byte, error) {
respCh := make(chan []byte, 1)
err := ctrlPoint.EnableNotifications(func(buf []byte) {
respCh <- buf
})
if err != nil {
return nil, err
}
data := <-respCh
ctrlPoint.EnableNotifications(nil)
if !bytes.HasPrefix(data, expect) {
return nil, fmt.Errorf("unexpected dfu response %x (expected %x)", data, expect)
}
return bytes.TrimPrefix(data, expect), nil
}
+617
View File
@@ -0,0 +1,617 @@
package infinitime
import (
"errors"
"io"
"io/fs"
"math"
"path"
"strings"
"sync"
"sync/atomic"
"go.elara.ws/itd/internal/fsproto"
"tinygo.org/x/bluetooth"
)
// FS represents a remote BLE filesystem
type FS struct {
mtx sync.Mutex
dev *Device
}
// Stat gets information about a file at the given path.
//
// WARNING: Since there's no stat command in the BLE FS protocol,
// this function does a ReadDir and then finds the requested file
// in the results, which makes it pretty slow.
func (ifs *FS) Stat(p string) (fs.FileInfo, error) {
dir := path.Dir(p)
entries, err := ifs.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.Name() == path.Base(p) {
return entry.Info()
}
}
return nil, fsproto.ErrFileNotExists
}
// Remove removes a file or empty directory at the given path.
//
// For a function that removes directories recursively, see [FS.RemoveAll]
func (ifs *FS) Remove(path string) error {
ifs.mtx.Lock()
defer ifs.mtx.Unlock()
char, err := ifs.dev.getChar(fsTransferChar)
if err != nil {
return err
}
return ifs.requestThenAwaitResponse(
char,
fsproto.DeleteFileOpcode,
fsproto.DeleteFileRequest{
PathLen: uint16(len(path)),
Path: path,
},
func(buf []byte) (bool, error) {
var mdr fsproto.DeleteFileResponse
return true, fsproto.ReadResponse(buf, fsproto.DeleteFileResp, &mdr)
},
)
}
// Rename moves a file or directory from an old path to a new path.
func (ifs *FS) Rename(old, new string) error {
ifs.mtx.Lock()
defer ifs.mtx.Unlock()
char, err := ifs.dev.getChar(fsTransferChar)
if err != nil {
return err
}
return ifs.requestThenAwaitResponse(
char,
fsproto.MoveFileOpcode,
fsproto.MoveFileRequest{
OldPathLen: uint16(len(old)),
OldPath: old,
NewPathLen: uint16(len(new)),
NewPath: new,
},
func(buf []byte) (bool, error) {
var mfr fsproto.MoveFileResponse
return true, fsproto.ReadResponse(buf, fsproto.MoveFileResp, &mfr)
},
)
}
// Mkdir creates a new directory at the specified path.
//
// For a function that creates necessary parents as well, see [FS.MkdirAll]
func (ifs *FS) Mkdir(path string) error {
ifs.mtx.Lock()
defer ifs.mtx.Unlock()
char, err := ifs.dev.getChar(fsTransferChar)
if err != nil {
return err
}
return ifs.requestThenAwaitResponse(
char,
fsproto.MakeDirectoryOpcode,
fsproto.MkdirRequest{
PathLen: uint16(len(path)),
Path: path,
},
func(buf []byte) (bool, error) {
var mdr fsproto.MkdirResponse
return true, fsproto.ReadResponse(buf, fsproto.MakeDirectoryResp, &mdr)
},
)
}
// ReadDir reads the directory at the specified path and returns a list of directory entries.
func (ifs *FS) ReadDir(path string) ([]fs.DirEntry, error) {
ifs.mtx.Lock()
defer ifs.mtx.Unlock()
char, err := ifs.dev.getChar(fsTransferChar)
if err != nil {
return nil, err
}
var out []fs.DirEntry
return out, ifs.requestThenAwaitResponse(
char,
fsproto.ListDirectoryOpcode,
fsproto.ListDirRequest{
PathLen: uint16(len(path)),
Path: path,
},
func(buf []byte) (bool, error) {
var ldr fsproto.ListDirResponse
err := fsproto.ReadResponse(buf, fsproto.ListDirectoryResp, &ldr)
if err != nil {
return true, err
}
if ldr.EntryNum == ldr.TotalEntries {
return true, nil
}
out = append(out, DirEntry{
flags: ldr.Flags,
modtime: ldr.ModTime,
size: ldr.FileSize,
path: string(ldr.Path),
})
return false, nil
},
)
}
// RemoveAll removes the file at the specified path and any children it contains,
// similar to the rm -r command.
func (ifs *FS) RemoveAll(p string) error {
if p == "" {
return nil
}
if path.Clean(p) == "/" {
return fsproto.ErrNoRemoveRoot
}
fi, err := ifs.Stat(p)
if err != nil {
return nil
}
if fi.IsDir() {
return ifs.removeWithChildren(p)
} else {
err = ifs.Remove(p)
var code int8
if err, ok := err.(fsproto.Error); ok {
code = err.Code
}
if err != nil && code != -2 {
return err
}
}
return nil
}
// removeWithChildren removes the directory at the given path and its children recursively.
func (ifs *FS) removeWithChildren(p string) error {
list, err := ifs.ReadDir(p)
if err != nil {
return err
}
for _, entry := range list {
name := entry.Name()
if name == "." || name == ".." {
continue
}
entryPath := path.Join(p, name)
if entry.IsDir() {
err = ifs.removeWithChildren(entryPath)
} else {
err = ifs.Remove(entryPath)
}
var code int8
if err, ok := err.(fsproto.Error); ok {
code = err.Code
}
if err != nil && code != -2 {
return err
}
}
return ifs.Remove(p)
}
// MkdirAll creates a directory and any necessary parents in the file system,
// similar to the mkdir -p command.
func (ifs *FS) MkdirAll(path string) error {
if path == "" || path == "/" {
return nil
}
splitPath := strings.Split(path, "/")
for i := 1; i < len(splitPath); i++ {
curPath := strings.Join(splitPath[0:i+1], "/")
err := ifs.Mkdir(curPath)
var code int8
if err, ok := err.(fsproto.Error); ok {
code = err.Code
}
if err != nil && code != -17 {
return err
}
}
return nil
}
var _ fs.File = (*File)(nil)
// File represents a remote file on a BLE filesystem.
//
// If ProgressFunc is set, it will be called whenever a read or write happens
// with the amount of bytes transferred and the total size of the file.
type File struct {
fs *FS
path string
offset uint32
size uint32
readOnly bool
closed bool
ProgressFunc func(transferred, total uint32)
}
// Open opens an existing file at the specified path.
// It returns a handle for the file and an error, if any.
func (ifs *FS) Open(path string) (*File, error) {
return &File{
fs: ifs,
path: path,
offset: 0,
readOnly: true,
}, nil
}
// Create creates a new file with the specified path and size.
// It returns a handle for the created file and an error, if any.
func (ifs *FS) Create(path string, size uint32) (*File, error) {
return &File{
fs: ifs,
path: path,
offset: 0,
size: size,
}, nil
}
// Write writes data from the byte slice b to the file.
// It returns the number of bytes written and an error, if any.
func (fl *File) Write(b []byte) (int, error) {
if fl.closed {
return 0, fsproto.ErrFileClosed
}
if fl.readOnly {
return 0, fsproto.ErrFileReadOnly
}
fl.fs.mtx.Lock()
defer fl.fs.mtx.Unlock()
char, err := fl.fs.dev.getChar(fsTransferChar)
if err != nil {
return 0, err
}
defer char.EnableNotifications(nil)
var chunkLen uint32
dataLen := uint32(len(b))
transferred := uint32(0)
mtu := uint32(fl.fs.mtu(char))
// continueCh is used to prevent race conditions. When the
// request loop starts, it reads from continueCh, blocking it
// until it's "released" by the notification function after
// the response is processed.
continueCh := make(chan struct{}, 2)
var notifErr error
err = char.EnableNotifications(func(buf []byte) {
var wfr fsproto.WriteFileResponse
err = fsproto.ReadResponse(buf, fsproto.WriteFileResp, &wfr)
if err != nil {
notifErr = err
char.EnableNotifications(nil)
close(continueCh)
return
}
transferred += chunkLen
fl.offset += chunkLen
if wfr.FreeSpace == 0 || transferred == dataLen {
char.EnableNotifications(nil)
close(continueCh)
return
}
if fl.ProgressFunc != nil {
fl.ProgressFunc(transferred, fl.size)
}
// Release the request loop
continueCh <- struct{}{}
})
err = fsproto.WriteRequest(char, fsproto.WriteFileHeaderOpcode, fsproto.WriteFileHeaderRequest{
PathLen: uint16(len(fl.path)),
Offset: fl.offset,
FileSize: fl.size,
Path: fl.path,
})
if err != nil {
return int(transferred), err
}
for range continueCh {
if notifErr != nil {
return int(transferred), notifErr
}
amountLeft := dataLen - transferred
chunkLen = mtu
if amountLeft < mtu {
chunkLen = amountLeft
}
err = fsproto.WriteRequest(char, fsproto.WriteFileOpcode, fsproto.WriteFileRequest{
Status: 0x01,
Offset: fl.offset,
ChunkLen: chunkLen,
Data: b[transferred : transferred+chunkLen],
})
if err != nil {
return int(transferred), err
}
}
return int(transferred), notifErr
}
// Read reads data from the file into the byte slice b.
// It returns the number of bytes read and an error, if any.
func (fl *File) Read(b []byte) (int, error) {
if fl.closed {
return 0, fsproto.ErrFileClosed
}
fl.fs.mtx.Lock()
defer fl.fs.mtx.Unlock()
char, err := fl.fs.dev.getChar(fsTransferChar)
if err != nil {
return 0, err
}
defer char.EnableNotifications(nil)
transferred := uint32(0)
maxLen := uint32(len(b))
mtu := uint32(fl.fs.mtu(char))
var (
notifErr error
done bool
)
// continueCh is used to prevent race conditions. When the
// request loop starts, it reads from continueCh, blocking it
// until it's "released" by the notification function after
// the response is processed.
continueCh := make(chan struct{}, 2)
err = char.EnableNotifications(func(buf []byte) {
var rfr fsproto.ReadFileResponse
err = fsproto.ReadResponse(buf, fsproto.ReadFileResp, &rfr)
if err != nil {
notifErr = err
char.EnableNotifications(nil)
close(continueCh)
return
}
fl.size = rfr.FileSize
if rfr.Offset == rfr.FileSize || rfr.ChunkLen == 0 {
notifErr = io.EOF
done = true
char.EnableNotifications(nil)
close(continueCh)
return
}
n := copy(b[transferred:], rfr.Data[:rfr.ChunkLen])
fl.offset += uint32(n)
transferred += uint32(n)
if fl.ProgressFunc != nil {
fl.ProgressFunc(transferred, rfr.FileSize)
}
// Release the request loop
continueCh <- struct{}{}
})
if err != nil {
return 0, err
}
defer char.EnableNotifications(nil)
amountLeft := maxLen - transferred
chunkLen := mtu
if amountLeft < mtu {
chunkLen = amountLeft
}
err = fsproto.WriteRequest(char, fsproto.ReadFileHeaderOpcode, fsproto.ReadFileHeaderRequest{
PathLen: uint16(len(fl.path)),
Offset: fl.offset,
ReadLen: chunkLen,
Path: fl.path,
})
if err != nil {
return 0, err
}
if notifErr != nil {
return int(transferred), notifErr
}
for !done {
// Wait for the notification function to release the loop
<-continueCh
if notifErr != nil {
return int(transferred), notifErr
}
amountLeft = maxLen - transferred
chunkLen = mtu
if amountLeft < mtu {
chunkLen = amountLeft
}
err = fsproto.WriteRequest(char, fsproto.ReadFileOpcode, fsproto.ReadFileRequest{
Status: 0x01,
Offset: fl.offset,
ReadLen: chunkLen,
})
if err != nil {
return int(transferred), err
}
}
return int(transferred), notifErr
}
// Stat returns information about the file,
func (fl *File) Stat() (fs.FileInfo, error) {
return fl.fs.Stat(fl.path)
}
// Seek sets the offset for the next Read or Write on the file to the specified offset.
// The whence parameter specifies the seek reference point:
//
// io.SeekStart: offset is relative to the start of the file.
// io.SeekCurrent: offset is relative to the current offset.
// io.SeekEnd: offset is relative to the end of the file.
//
// Seek returns the new offset and an error, if any.
func (fl *File) Seek(offset int64, whence int) (int64, error) {
if fl.closed {
return 0, fsproto.ErrFileClosed
}
if offset > math.MaxUint32 {
return 0, fsproto.ErrInvalidOffset
}
u32Offset := uint32(offset)
fl.fs.mtx.Lock()
defer fl.fs.mtx.Unlock()
if fl.size == 0 {
return 0, errors.New("file size unknown")
}
var newOffset uint32
switch whence {
case io.SeekStart:
newOffset = u32Offset
case io.SeekCurrent:
newOffset = fl.offset + u32Offset
case io.SeekEnd:
newOffset = fl.size + u32Offset
}
if newOffset > fl.size || newOffset < 0 {
return 0, fsproto.ErrInvalidOffset
}
fl.offset = newOffset
return int64(fl.offset), nil
}
// Close closes the file for future operations
func (fl *File) Close() error {
fl.fs.mtx.Lock()
defer fl.fs.mtx.Unlock()
fl.closed = true
return nil
}
// requestThenAwaitResponse executes a BLE FS request and then waits for one or more responses,
// until fn returns true or an error is encountered.
func (ifs *FS) requestThenAwaitResponse(char *bluetooth.DeviceCharacteristic, opcode fsproto.FSReqOpcode, req any, fn func(buf []byte) (bool, error)) error {
var stopped atomic.Bool
errCh := make(chan error, 1)
char.EnableNotifications(func(buf []byte) {
stop, err := fn(buf)
if err != nil && !stopped.Load() {
errCh <- err
char.EnableNotifications(nil)
return
} else if !stopped.Load() {
errCh <- nil
}
if stop && !stopped.Load() {
stopped.Store(true)
close(errCh)
char.EnableNotifications(nil)
}
})
defer char.EnableNotifications(nil)
err := fsproto.WriteRequest(char, opcode, req)
if err != nil {
return err
}
for err := range errCh {
if err != nil {
return err
}
}
return nil
}
func (ifs *FS) mtu(char *bluetooth.DeviceCharacteristic) uint16 {
mtuVal, _ := char.GetMTU()
if mtuVal == 0 {
mtuVal = 256
}
return mtuVal - 20
}
var _ fs.FS = (*GoFS)(nil)
var _ fs.StatFS = (*GoFS)(nil)
var _ fs.ReadDirFS = (*GoFS)(nil)
// GoFS implements [io/fs.FS], [io/fs.StatFS], and [io/fs.ReadDirFS]
// for the InfiniTime filesystem
type GoFS struct {
*FS
}
// Open opens an existing file at the specified path.
// It returns a handle for the file and an error, if any.
func (gfs GoFS) Open(path string) (fs.File, error) {
return gfs.FS.Open(path)
}
+142
View File
@@ -0,0 +1,142 @@
package infinitime
import (
"fmt"
"io/fs"
"strconv"
"time"
)
// DirEntry represents an entry from a directory listing
type DirEntry struct {
flags uint32
modtime uint64
size uint32
path string
}
// Name returns the name of the file described by the entry
func (de DirEntry) Name() string {
return de.path
}
// IsDir reports whether the entry describes a directory.
func (de DirEntry) IsDir() bool {
return de.flags&0b1 == 1
}
// Type returns the type bits for the entry.
func (de DirEntry) Type() fs.FileMode {
if de.IsDir() {
return fs.ModeDir
} else {
return 0
}
}
// Info returns the FileInfo for the file or subdirectory described by the entry.
func (de DirEntry) Info() (fs.FileInfo, error) {
return FileInfo{
name: de.path,
size: de.size,
modtime: de.modtime,
mode: de.Type(),
isDir: de.IsDir(),
}, nil
}
func (de DirEntry) String() string {
var isDirChar rune
if de.IsDir() {
isDirChar = 'd'
} else {
isDirChar = '-'
}
// Get human-readable value for file size
val, unit := bytesHuman(de.size)
prec := 0
// If value is less than 10, set precision to 1
if val < 10 {
prec = 1
}
// Convert float to string
valStr := strconv.FormatFloat(val, 'f', prec, 64)
// Return string formatted like so:
// - 10 kB file
// or:
// d 0 B .
return fmt.Sprintf(
"%c %3s %-2s %s",
isDirChar,
valStr,
unit,
de.path,
)
}
func bytesHuman(b uint32) (float64, string) {
const unit = 1000
// Set possible unit prefixes (PineTime flash is 4MB)
units := [2]rune{'k', 'M'}
// If amount of bytes is less than smallest unit
if b < unit {
// Return unchanged with unit "B"
return float64(b), "B"
}
div, exp := uint32(unit), 0
// Get decimal values and unit prefix index
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
// Create string for full unit
unitStr := string([]rune{units[exp], 'B'})
// Return decimal with unit string
return float64(b) / float64(div), unitStr
}
// FileInfo implements fs.FileInfo
type FileInfo struct {
name string
size uint32
modtime uint64
mode fs.FileMode
isDir bool
}
// Name returns the base name of the file
func (fi FileInfo) Name() string {
return fi.name
}
// Size returns the total size of the file
func (fi FileInfo) Size() int64 {
return int64(fi.size)
}
// Mode returns the mode of the file
func (fi FileInfo) Mode() fs.FileMode {
return fi.mode
}
// ModTime returns the modification time of the file
// As of now, this is unimplemented in InfiniTime, and
// will always return 0.
func (fi FileInfo) ModTime() time.Time {
return time.Unix(0, int64(fi.modtime))
}
// IsDir returns whether the file is a directory
func (fi FileInfo) IsDir() bool {
return fi.isDir
}
// Sys is unimplemented and returns nil
func (fi FileInfo) Sys() any {
return nil
}
+173
View File
@@ -0,0 +1,173 @@
package infinitime
import (
"fmt"
"sync"
"sync/atomic"
"time"
"tinygo.org/x/bluetooth"
)
type Options struct {
Allowlist []string
Blocklist []string
ScanInterval time.Duration
OnDisconnect func(dev *Device)
OnReconnect func(dev *Device)
OnConnect func(dev *Device)
}
func reconnect(opts Options, adapter *bluetooth.Adapter, device *Device, mac string) {
if device == nil {
return
}
done := false
for {
adapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) {
if sr.Address.String() != mac {
return
}
dev, err := a.Connect(sr.Address, bluetooth.ConnectionParams{})
if err != nil {
return
}
adapter.StopScan()
device.deviceMtx.Lock()
device.device = dev
device.deviceMtx.Unlock()
device.notifierMtx.Lock()
for char, notifier := range device.notifierMap {
c, err := device.getChar(char)
if err != nil {
continue
}
err = c.EnableNotifications(nil)
if err != nil {
continue
}
err = c.EnableNotifications(notifier.notify)
if err != nil {
continue
}
}
device.notifierMtx.Unlock()
done = true
})
if done {
return
}
time.Sleep(opts.ScanInterval)
}
}
func Connect(opts Options) (device *Device, err error) {
adapter := bluetooth.DefaultAdapter
if opts.ScanInterval == 0 {
opts.ScanInterval = 2 * time.Minute
}
var mac string
adapter.SetConnectHandler(func(dev bluetooth.Device, connected bool) {
if mac == "" || dev.Address.String() != mac {
return
}
if connected {
if opts.OnReconnect != nil {
opts.OnReconnect(device)
}
} else {
if opts.OnDisconnect != nil {
opts.OnDisconnect(device)
}
go reconnect(opts, adapter, device, mac)
}
})
err = adapter.Enable()
if err != nil {
return nil, err
}
var scanErr error
err = adapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) {
if sr.LocalName() != "InfiniTime" {
return
}
dev, err := a.Connect(sr.Address, bluetooth.ConnectionParams{})
if err != nil {
scanErr = err
adapter.StopScan()
return
}
mac = dev.Address.String()
device = &Device{adapter: a, device: dev, notifierMap: map[btChar]notifier{}}
if opts.OnConnect != nil {
opts.OnConnect(device)
}
adapter.StopScan()
})
if err != nil {
return nil, err
}
if scanErr != nil {
return nil, scanErr
}
return device, nil
}
// Device represents an InfiniTime device
type Device struct {
adapter *bluetooth.Adapter
deviceMtx sync.Mutex
device bluetooth.Device
updating atomic.Bool
notifierMtx sync.Mutex
notifierMap map[btChar]notifier
}
// FS returns a handle for InifniTime's filesystem'
func (d *Device) FS() *FS {
return &FS{
dev: d,
}
}
func (d *Device) getChar(c btChar) (*bluetooth.DeviceCharacteristic, error) {
if d.updating.Load() {
return nil, fmt.Errorf("device is currently updating")
}
d.deviceMtx.Lock()
defer d.deviceMtx.Unlock()
services, err := d.device.DiscoverServices([]bluetooth.UUID{c.ServiceID})
if err != nil {
return nil, fmt.Errorf("characteristic %s (%s) not found", c.ID, c.Name)
}
chars, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{c.ID})
if err != nil {
return nil, fmt.Errorf("characteristic %s (%s) not found", c.ID, c.Name)
}
return chars[0], err
}
+101
View File
@@ -0,0 +1,101 @@
package infinitime
import (
"context"
"encoding/binary"
)
// Address returns the MAC address of the connected device.
func (d *Device) Address() string {
return d.device.Address.String()
}
// Version returns the version of InifniTime that the connected device is running.
func (d *Device) Version() (string, error) {
c, err := d.getChar(firmwareVerChar)
if err != nil {
return "", err
}
ver := make([]byte, 16)
n, err := c.Read(ver)
return string(ver[:n]), err
}
// BatteryLevel returns the current battery level of the connected PineTime.
func (d *Device) BatteryLevel() (lvl uint8, err error) {
c, err := d.getChar(batteryLevelChar)
if err != nil {
return 0, err
}
err = binary.Read(c, binary.LittleEndian, &lvl)
return lvl, err
}
// WatchBatteryLevel calls fn whenever the battery level changes.
func (d *Device) WatchBatteryLevel(ctx context.Context, fn func(level uint8, err error)) error {
return watchChar(ctx, d, batteryLevelChar, fn)
}
// StepCount returns the current step count recorded on the watch.
func (d *Device) StepCount() (sc uint32, err error) {
c, err := d.getChar(stepCountChar)
if err != nil {
return 0, err
}
err = binary.Read(c, binary.LittleEndian, &sc)
return sc, err
}
// WatchStepCount calls fn whenever the step count changes.
func (d *Device) WatchStepCount(ctx context.Context, fn func(count uint32, err error)) error {
return watchChar(ctx, d, stepCountChar, fn)
}
// HeartRate returns the current heart rate recorded on the watch.
func (d *Device) HeartRate() (uint8, error) {
c, err := d.getChar(heartRateChar)
if err != nil {
return 0, err
}
data := make([]byte, 2)
_, err = c.Read(data)
if err != nil {
return 0, err
}
return data[1], nil
}
// WatchHeartRate calls fn whenever the heart rate changes.
func (d *Device) WatchHeartRate(ctx context.Context, fn func(rate uint8, err error)) error {
return watchChar(ctx, d, heartRateChar, func(rate [2]uint8, err error) {
fn(rate[1], err)
})
}
// MotionValues represents gyroscope coordinates.
type MotionValues struct {
X int16
Y int16
Z int16
}
// Motion returns the current gyroscope coordinates of the PineTime.
func (d *Device) Motion() (mv MotionValues, err error) {
c, err := d.getChar(rawMotionChar)
if err != nil {
return MotionValues{}, err
}
err = binary.Read(c, binary.LittleEndian, &mv)
return mv, err
}
// WatchMotion calls fn whenever the gyroscope coordinates change.
func (d *Device) WatchMotion(ctx context.Context, fn func(level MotionValues, err error)) error {
return watchChar(ctx, d, rawMotionChar, fn)
}
+68
View File
@@ -0,0 +1,68 @@
package infinitime
import "context"
type MusicEvent uint8
const (
MusicEventOpen MusicEvent = 0xe0
MusicEventPlay MusicEvent = 0x00
MusicEventPause MusicEvent = 0x01
MusicEventNext MusicEvent = 0x03
MusicEventPrev MusicEvent = 0x04
MusicEventVolUp MusicEvent = 0x05
MusicEventVolDown MusicEvent = 0x06
)
// SetMusicStatus sets whether the music is playing or paused.
func (d *Device) SetMusicStatus(playing bool) error {
char, err := d.getChar(musicStatusChar)
if err != nil {
return err
}
if playing {
_, err = char.WriteWithoutResponse([]byte{0x1})
} else {
_, err = char.WriteWithoutResponse([]byte{0x0})
}
return err
}
// SetMusicArtist sets the music artist.
func (d *Device) SetMusicArtist(artist string) error {
char, err := d.getChar(musicArtistChar)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(artist))
return err
}
// SetMusicTrack sets the music track name.
func (d *Device) SetMusicTrack(track string) error {
char, err := d.getChar(musicTrackChar)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(track))
return err
}
// SetMusicAlbum sets the music album name.
func (d *Device) SetMusicAlbum(album string) error {
char, err := d.getChar(musicAlbumChar)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(album))
return err
}
// WatchMusicEvents calls fn whenever the InfiniTime music app broadcasts an event.
func (d *Device) WatchMusicEvents(ctx context.Context, fn func(event MusicEvent, err error)) error {
return watchChar(ctx, d, musicEventChar, fn)
}
+137
View File
@@ -0,0 +1,137 @@
package infinitime
type NavFlag string
const (
NavFlagArrive NavFlag = "arrive"
NavFlagArriveLeft NavFlag = "arrive-left"
NavFlagArriveRight NavFlag = "arrive-right"
NavFlagArriveStraight NavFlag = "arrive-straight"
NavFlagClose NavFlag = "close"
NavFlagContinue NavFlag = "continue"
NavFlagContinueLeft NavFlag = "continue-left"
NavFlagContinueRight NavFlag = "continue-right"
NavFlagContinueSlightLeft NavFlag = "continue-slight-left"
NavFlagContinueSlightRight NavFlag = "continue-slight-right"
NavFlagContinueStraight NavFlag = "continue-straight"
NavFlagContinueUturn NavFlag = "continue-uturn"
NavFlagDepart NavFlag = "depart"
NavFlagDepartLeft NavFlag = "depart-left"
NavFlagDepartRight NavFlag = "depart-right"
NavFlagDepartStraight NavFlag = "depart-straight"
NavFlagEndOfRoadLeft NavFlag = "end-of-road-left"
NavFlagEndOfRoadRight NavFlag = "end-of-road-right"
NavFlagFerry NavFlag = "ferry"
NavFlagFlag NavFlag = "flag"
NavFlagFork NavFlag = "fork"
NavFlagForkLeft NavFlag = "fork-left"
NavFlagForkRight NavFlag = "fork-right"
NavFlagForkSlightLeft NavFlag = "fork-slight-left"
NavFlagForkSlightRight NavFlag = "fork-slight-right"
NavFlagForkStraight NavFlag = "fork-straight"
NavFlagInvalid NavFlag = "invalid"
NavFlagInvalidLeft NavFlag = "invalid-left"
NavFlagInvalidRight NavFlag = "invalid-right"
NavFlagInvalidSlightLeft NavFlag = "invalid-slight-left"
NavFlagInvalidSlightRight NavFlag = "invalid-slight-right"
NavFlagInvalidStraight NavFlag = "invalid-straight"
NavFlagInvalidUturn NavFlag = "invalid-uturn"
NavFlagMergeLeft NavFlag = "merge-left"
NavFlagMergeRight NavFlag = "merge-right"
NavFlagMergeSlightLeft NavFlag = "merge-slight-left"
NavFlagMergeSlightRight NavFlag = "merge-slight-right"
NavFlagMergeStraight NavFlag = "merge-straight"
NavFlagNewNameLeft NavFlag = "new-name-left"
NavFlagNewNameRight NavFlag = "new-name-right"
NavFlagNewNameSharpLeft NavFlag = "new-name-sharp-left"
NavFlagNewNameSharpRight NavFlag = "new-name-sharp-right"
NavFlagNewNameSlightLeft NavFlag = "new-name-slight-left"
NavFlagNewNameSlightRight NavFlag = "new-name-slight-right"
NavFlagNewNameStraight NavFlag = "new-name-straight"
NavFlagNotificationLeft NavFlag = "notification-left"
NavFlagNotificationRight NavFlag = "notification-right"
NavFlagNotificationSharpLeft NavFlag = "notification-sharp-left"
NavFlagNotificationSharpRight NavFlag = "notification-sharp-right"
NavFlagNotificationSlightLeft NavFlag = "notification-slight-left"
NavFlagNotificationSlightRight NavFlag = "notification-slight-right"
NavFlagNotificationStraight NavFlag = "notification-straight"
NavFlagOffRampLeft NavFlag = "off-ramp-left"
NavFlagOffRampRight NavFlag = "off-ramp-right"
NavFlagOffRampSharpLeft NavFlag = "off-ramp-sharp-left"
NavFlagOffRampSharpRight NavFlag = "off-ramp-sharp-right"
NavFlagOffRampSlightLeft NavFlag = "off-ramp-slight-left"
NavFlagOffRampSlightRight NavFlag = "off-ramp-slight-right"
NavFlagOffRampStraight NavFlag = "off-ramp-straight"
NavFlagOnRampLeft NavFlag = "on-ramp-left"
NavFlagOnRampRight NavFlag = "on-ramp-right"
NavFlagOnRampSharpLeft NavFlag = "on-ramp-sharp-left"
NavFlagOnRampSharpRight NavFlag = "on-ramp-sharp-right"
NavFlagOnRampSlightLeft NavFlag = "on-ramp-slight-left"
NavFlagOnRampSlightRight NavFlag = "on-ramp-slight-right"
NavFlagOnRampStraight NavFlag = "on-ramp-straight"
NavFlagRotary NavFlag = "rotary"
NavFlagRotaryLeft NavFlag = "rotary-left"
NavFlagRotaryRight NavFlag = "rotary-right"
NavFlagRotarySharpLeft NavFlag = "rotary-sharp-left"
NavFlagRotarySharpRight NavFlag = "rotary-sharp-right"
NavFlagRotarySlightLeft NavFlag = "rotary-slight-left"
NavFlagRotarySlightRight NavFlag = "rotary-slight-right"
NavFlagRotaryStraight NavFlag = "rotary-straight"
NavFlagRoundabout NavFlag = "roundabout"
NavFlagRoundaboutLeft NavFlag = "roundabout-left"
NavFlagRoundaboutRight NavFlag = "roundabout-right"
NavFlagRoundaboutSharpLeft NavFlag = "roundabout-sharp-left"
NavFlagRoundaboutSharpRight NavFlag = "roundabout-sharp-right"
NavFlagRoundaboutSlightLeft NavFlag = "roundabout-slight-left"
NavFlagRoundaboutSlightRight NavFlag = "roundabout-slight-right"
NavFlagRoundaboutStraight NavFlag = "roundabout-straight"
NavFlagTurnLeft NavFlag = "turn-left"
NavFlagTurnRight NavFlag = "turn-right"
NavFlagTurnSharpLeft NavFlag = "turn-sharp-left"
NavFlagTurnSharpRight NavFlag = "turn-sharp-right"
NavFlagTurnSlightLeft NavFlag = "turn-slight-left"
NavFlagTurnSlightRight NavFlag = "turn-slight-right"
NavFlagTurnStraight NavFlag = "turn-straight"
NavFlagUpDown NavFlag = "updown"
NavFlagUTurn NavFlag = "uturn"
)
// SetNavFlag sets the navigation flag icon.
func (d *Device) SetNavFlag(flag NavFlag) error {
char, err := d.getChar(navigationFlagsChar)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(flag))
return err
}
// SetNavNarrative sets the navigation narrative string.
func (d *Device) SetNavNarrative(narrative string) error {
char, err := d.getChar(navigationNarrativeChar)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(narrative))
return err
}
// SetNavManeuverDistance sets the navigation maneuver distance.
func (d *Device) SetNavManeuverDistance(manDist string) error {
char, err := d.getChar(navigationManDist)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte(manDist))
return err
}
// SetNavProgress sets the navigation progress.
func (d *Device) SetNavProgress(progress uint8) error {
char, err := d.getChar(navigationProgress)
if err != nil {
return err
}
_, err = char.WriteWithoutResponse([]byte{progress})
return err
}
+42
View File
@@ -0,0 +1,42 @@
package infinitime
var (
regularNotifHeader = []byte{0x00, 0x01, 0x00}
callNotifHeader = []byte{0x03, 0x01, 0x00}
)
// Notify sends a notification to the PineTime using the Alert Notification Service
func (d *Device) Notify(title, body string) error {
c, err := d.getChar(newAlertChar)
if err != nil {
return err
}
content := title + "\x00" + body
_, err = c.WriteWithoutResponse(append(regularNotifHeader, content...))
return err
}
type CallStatus uint8
const (
CallStatusDeclined CallStatus = iota
CallStatusAccepted
CallStatusMuted
)
// NotifyCall sends a call to the PineTime using the Alert Notification Service,
// then executes fn once the user presses a button on the watch.
func (d *Device) NotifyCall(from string, fn func(CallStatus)) error {
c, err := d.getChar(newAlertChar)
if err != nil {
return err
}
_, err = c.WriteWithoutResponse(append(callNotifHeader, from...))
if err != nil {
return err
}
return watchCharOnce(d, notifEventChar, fn)
}
+135
View File
@@ -0,0 +1,135 @@
package infinitime
import (
"archive/zip"
"encoding/json"
"errors"
"io"
"path/filepath"
)
type ResourceOperation int
const (
// ResourceUpload represents the upload phase
// of resource loading
ResourceUpload = iota
// ResourceRemove represents the obsolete
// file removal phase of resource loading
ResourceRemove
)
// resourceManifest is the structure of the resource manifest file
type resourceManifest struct {
Resources []resource `json:"resources"`
Obsolete []obsoleteResource `json:"obsolete_files"`
}
// resource represents a resource entry in the manifest
type resource struct {
Name string `json:"filename"`
Path string `json:"path"`
}
// obsoleteResource represents an obsolete file entry in the manifest
type obsoleteResource struct {
Path string `json:"path"`
Since string `json:"since"`
}
// ResourceLoadProgress contains information on the progress of
// a resource load
type ResourceLoadProgress struct {
Operation ResourceOperation
Name string
Total uint32
Transferred uint32
}
// LoadResources accepts the path of an InfiniTime resource archive and loads its contents to the watch's filesystem.
func LoadResources(archivePath string, fs *FS, progress func(ResourceLoadProgress)) error {
r, err := zip.OpenReader(archivePath)
if err != nil {
return err
}
defer r.Close()
manifestFl, err := r.Open("resources.json")
if err != nil {
return err
}
var manifest resourceManifest
err = json.NewDecoder(manifestFl).Decode(&manifest)
if err != nil {
return err
}
err = manifestFl.Close()
if err != nil {
return err
}
for _, file := range manifest.Obsolete {
err := fs.RemoveAll(file.Path)
if err != nil {
return err
}
progress(ResourceLoadProgress{
Operation: ResourceRemove,
Name: filepath.Base(file.Path),
})
}
for _, file := range manifest.Resources {
src, err := r.Open(file.Name)
if err != nil {
return err
}
fi, err := src.Stat()
if err != nil {
return err
}
err = fs.MkdirAll(filepath.Dir(file.Path))
if err != nil {
return err
}
dst, err := fs.Create(file.Path, uint32(fi.Size()))
if err != nil {
return err
}
dst.ProgressFunc = func(transferred, total uint32) {
progress(ResourceLoadProgress{
Name: file.Name,
Transferred: transferred,
Total: total,
})
}
_, err = io.Copy(dst, src)
if err != nil {
return errors.Join(
err,
src.Close(),
dst.Close(),
)
}
err = src.Close()
if err != nil {
return err
}
err = dst.Close()
if err != nil {
return err
}
}
return nil
}
+58
View File
@@ -0,0 +1,58 @@
package infinitime
import (
"bytes"
"encoding/binary"
"time"
)
// SetTime sets the current time, and then sets the timezone data,
// if the local time characteristic is available.
func (d *Device) SetTime(t time.Time) error {
c, err := d.getChar(currentTimeChar)
if err != nil {
return err
}
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, uint16(t.Year()))
binary.Write(buf, binary.LittleEndian, uint8(t.Month()))
binary.Write(buf, binary.LittleEndian, uint8(t.Day()))
binary.Write(buf, binary.LittleEndian, uint8(t.Hour()))
binary.Write(buf, binary.LittleEndian, uint8(t.Minute()))
binary.Write(buf, binary.LittleEndian, uint8(t.Second()))
binary.Write(buf, binary.LittleEndian, uint8(t.Weekday()))
binary.Write(buf, binary.LittleEndian, uint8((t.Nanosecond()/1000)/1e6*256))
binary.Write(buf, binary.LittleEndian, uint8(0b0001))
_, err = c.WriteWithoutResponse(buf.Bytes())
if err != nil {
return err
}
ltc, err := d.getChar(localTimeChar)
if err != nil {
return nil
}
_, offset := t.Zone()
dst := 0
// Local time expects two values: the timezone offset and the dst offset, both
// expressed in quarters of an hour.
// Timezone offset is to be constant over DST, with dst offset holding the offset != 0
// when DST is in effect.
// As there is no standard way in go to get the actual dst offset, we assume it to be 1h
// when DST is in effect
if t.IsDST() {
dst = 3600
offset -= 3600
}
buf.Reset()
binary.Write(buf, binary.LittleEndian, uint8(offset/3600*4))
binary.Write(buf, binary.LittleEndian, uint8(dst/3600*4))
_, err = ltc.WriteWithoutResponse(buf.Bytes())
return err
}
+108
View File
@@ -0,0 +1,108 @@
package infinitime
import (
"bytes"
"context"
"encoding/binary"
"sync"
"tinygo.org/x/bluetooth"
)
type notifier interface {
notify([]byte)
}
type watcher[T any] struct {
mu sync.Mutex
nextFuncID int
callbacks map[int]func(T, error)
char *bluetooth.DeviceCharacteristic
}
func (w *watcher[T]) addCallback(fn func(T, error)) int {
w.mu.Lock()
defer w.mu.Unlock()
funcID := w.nextFuncID
w.callbacks[funcID] = fn
w.nextFuncID++
return funcID
}
func (w *watcher[T]) notify(b []byte) {
var val T
err := binary.Read(bytes.NewReader(b), binary.LittleEndian, &val)
w.mu.Lock()
for _, fn := range w.callbacks {
go fn(val, err)
}
w.mu.Unlock()
}
func (w *watcher[T]) cancelFn(d *Device, ch btChar, id int) func() {
return func() {
w.mu.Lock()
delete(w.callbacks, id)
w.mu.Unlock()
if len(w.callbacks) == 0 {
d.notifierMtx.Lock()
delete(d.notifierMap, ch)
d.notifierMtx.Unlock()
w.char.EnableNotifications(nil)
}
}
}
func watchChar[T any](ctx context.Context, d *Device, ch btChar, fn func(T, error)) error {
d.notifierMtx.Lock()
defer d.notifierMtx.Unlock()
if n, ok := d.notifierMap[ch]; ok {
w := n.(*watcher[T])
funcID := w.addCallback(fn)
context.AfterFunc(ctx, w.cancelFn(d, ch, funcID))
go func() {
<-ctx.Done()
w.cancelFn(d, ch, funcID)()
}()
return nil
} else {
c, err := d.getChar(ch)
if err != nil {
return err
}
w := &watcher[T]{callbacks: map[int]func(T, error){}}
err = c.EnableNotifications(w.notify)
if err != nil {
return err
}
w.char = c
funcID := w.addCallback(fn)
d.notifierMap[ch] = w
context.AfterFunc(ctx, w.cancelFn(d, ch, funcID))
return nil
}
}
func watchCharOnce[T any](d *Device, ch btChar, fn func(T)) error {
ctx, cancel := context.WithCancel(context.Background())
var watchErr error
err := watchChar(ctx, d, ch, func(val T, err error) {
defer cancel()
if err != nil {
watchErr = err
return
}
fn(val)
})
if err != nil {
return err
}
<-ctx.Done()
return watchErr
}
+124
View File
@@ -0,0 +1,124 @@
package infinitime
import (
"bytes"
"encoding/binary"
"errors"
"time"
)
const (
weatherVersion = 0
currentWeatherType = 0
forecastWeatherType = 1
)
type WeatherIcon uint8
const (
WeatherIconClear WeatherIcon = iota
WeatherIconFewClouds
WeatherIconClouds
WeatherIconHeavyClouds
WeatherIconCloudsWithRain
WeatherIconRain
WeatherIconThunderstorm
WeatherIconSnow
WeatherIconMist
)
// CurrentWeather represents the current weather
type CurrentWeather struct {
Time time.Time
CurrentTemp float32
MinTemp float32
MaxTemp float32
Location string
Icon WeatherIcon
}
// Bytes returns the [CurrentWeather] struct encoded using the InfiniTime
// weather wire protocol.
func (cw CurrentWeather) Bytes() []byte {
buf := &bytes.Buffer{}
buf.WriteByte(currentWeatherType)
buf.WriteByte(weatherVersion)
_, offset := cw.Time.Zone()
binary.Write(buf, binary.LittleEndian, cw.Time.Unix()+int64(offset))
binary.Write(buf, binary.LittleEndian, int16(cw.CurrentTemp*100))
binary.Write(buf, binary.LittleEndian, int16(cw.MinTemp*100))
binary.Write(buf, binary.LittleEndian, int16(cw.MaxTemp*100))
location := make([]byte, 32)
copy(location, cw.Location)
buf.Write(location)
buf.WriteByte(byte(cw.Icon))
return buf.Bytes()
}
// Forecast represents a weather forecast
type Forecast struct {
Time time.Time
Days []ForecastDay
}
// ForecastDay represents a forecast for a single day
type ForecastDay struct {
MinTemp int16
MaxTemp int16
Icon WeatherIcon
}
// Bytes returns the [Forecast] struct encoded using the InfiniTime
// weather wire protocol.
func (f Forecast) Bytes() []byte {
buf := &bytes.Buffer{}
buf.WriteByte(forecastWeatherType)
buf.WriteByte(weatherVersion)
_, offset := f.Time.Zone()
binary.Write(buf, binary.LittleEndian, f.Time.Unix()+int64(offset))
buf.WriteByte(uint8(len(f.Days)))
for _, day := range f.Days {
binary.Write(buf, binary.LittleEndian, day.MinTemp*100)
binary.Write(buf, binary.LittleEndian, day.MaxTemp*100)
buf.WriteByte(byte(day.Icon))
}
return buf.Bytes()
}
// SetCurrentWeather updates the current weather data on the PineTime
func (d *Device) SetCurrentWeather(cw CurrentWeather) error {
c, err := d.getChar(weatherDataChar)
if err != nil {
return err
}
_, err = c.WriteWithoutResponse(cw.Bytes())
return err
}
// SetForecast sets future forecast data on the PineTime
func (d *Device) SetForecast(f Forecast) error {
c, err := d.getChar(weatherDataChar)
if err != nil {
return err
}
if len(f.Days) > 5 {
return errors.New("amount of forecast days exceeds maximum of 5")
}
_, err = c.WriteWithoutResponse(f.Bytes())
return err
}
+62
View File
@@ -0,0 +1,62 @@
package fsproto
import (
"errors"
"fmt"
)
var (
ErrFileNotExists = errors.New("file does not exist")
ErrFileReadOnly = errors.New("file is read only")
ErrFileWriteOnly = errors.New("file is write only")
ErrInvalidOffset = errors.New("offset out of range")
ErrNoRemoveRoot = errors.New("refusing to remove root directory")
ErrFileClosed = errors.New("cannot perform operation on a closed file")
)
// Error represents an error returned by BLE FS
type Error struct {
Code int8
}
// Error returns the string associated with the error code
func (err Error) Error() string {
switch err.Code {
case 0x02:
return "filesystem error"
case 0x05:
return "read-only filesystem"
case 0x03:
return "no such file"
case 0x04:
return "protocol error"
case -5:
return "input/output error"
case -84:
return "filesystem is corrupted"
case -2:
return "no such directory entry"
case -17:
return "entry already exists"
case -20:
return "entry is not a directory"
case -39:
return "directory is not empty"
case -9:
return "bad file number"
case -27:
return "file is too large"
case -22:
return "invalid parameter"
case -28:
return "no space left on device"
case -12:
return "no more memory available"
case -61:
return "no attr available"
case -36:
return "file name is too long"
default:
return fmt.Sprintf("unknown error (code %d)", err.Code)
}
}
+212
View File
@@ -0,0 +1,212 @@
package fsproto
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"reflect"
"tinygo.org/x/bluetooth"
)
type FSReqOpcode uint8
const (
ReadFileHeaderOpcode FSReqOpcode = 0x10
ReadFileOpcode FSReqOpcode = 0x12
WriteFileHeaderOpcode FSReqOpcode = 0x20
WriteFileOpcode FSReqOpcode = 0x22
DeleteFileOpcode FSReqOpcode = 0x30
MakeDirectoryOpcode FSReqOpcode = 0x40
ListDirectoryOpcode FSReqOpcode = 0x50
MoveFileOpcode FSReqOpcode = 0x60
)
type FSRespOpcode uint8
const (
ReadFileResp FSRespOpcode = 0x11
WriteFileResp FSRespOpcode = 0x21
DeleteFileResp FSRespOpcode = 0x31
MakeDirectoryResp FSRespOpcode = 0x41
ListDirectoryResp FSRespOpcode = 0x51
MoveFileResp FSRespOpcode = 0x61
)
type ReadFileHeaderRequest struct {
Padding byte
PathLen uint16
Offset uint32
ReadLen uint32
Path string
}
type ReadFileRequest struct {
Status uint8
Padding [2]byte
Offset uint32
ReadLen uint32
}
type ReadFileResponse struct {
Status int8
Padding [2]byte
Offset uint32
FileSize uint32
ChunkLen uint32
Data []byte
}
type WriteFileHeaderRequest struct {
Padding byte
PathLen uint16
Offset uint32
ModTime uint64
FileSize uint32
Path string
}
type WriteFileRequest struct {
Status uint8
Padding [2]byte
Offset uint32
ChunkLen uint32
Data []byte
}
type WriteFileResponse struct {
Status int8
Padding [2]byte
Offset uint32
ModTime uint64
FreeSpace uint32
}
type DeleteFileRequest struct {
Padding byte
PathLen uint16
Path string
}
type DeleteFileResponse struct {
Status int8
}
type MkdirRequest struct {
Padding byte
PathLen uint16
Padding2 [4]byte
Timestamp uint64
Path string
}
type MkdirResponse struct {
Status int8
Padding [6]byte
ModTime uint64
}
type ListDirRequest struct {
Padding byte
PathLen uint16
Path string
}
type ListDirResponse struct {
Status int8
PathLen uint16
EntryNum uint32
TotalEntries uint32
Flags uint32
ModTime uint64
FileSize uint32
Path []byte
}
type MoveFileRequest struct {
Padding byte
OldPathLen uint16
NewPathLen uint16
OldPath string
Padding2 byte
NewPath string
}
type MoveFileResponse struct {
Status int8
}
func WriteRequest(char *bluetooth.DeviceCharacteristic, opcode FSReqOpcode, req any) error {
buf := &bytes.Buffer{}
buf.WriteByte(byte(opcode))
rv := reflect.ValueOf(req)
for rv.Kind() == reflect.Pointer {
rv = rv.Elem()
}
for i := 0; i < rv.NumField(); i++ {
switch field := rv.Field(i); field.Kind() {
case reflect.String:
io.WriteString(buf, field.String())
case reflect.Slice:
if field.Type().Elem().Kind() == reflect.Uint8 {
buf.Write(field.Bytes())
}
default:
binary.Write(buf, binary.LittleEndian, field.Interface())
}
}
_, err := char.WriteWithoutResponse(buf.Bytes())
return err
}
func ReadResponse(b []byte, expect FSRespOpcode, out interface{}) error {
if len(b) == 0 {
return errors.New("empty response packet")
}
if opcode := FSRespOpcode(b[0]); opcode != expect {
return fmt.Errorf("unexpected response opcode: expected %x, got %x", expect, opcode)
}
r := bytes.NewReader(b[1:])
ot := reflect.TypeOf(out)
if ot.Kind() != reflect.Ptr || ot.Elem().Kind() != reflect.Struct {
return errors.New("out parameter must be a pointer to a struct")
}
ov := reflect.ValueOf(out).Elem()
for i := 0; i < ot.Elem().NumField(); i++ {
field := ot.Elem().Field(i)
fieldValue := ov.Field(i)
// If the last field is a byte slice, just read the remaining data into it and return.
if i == ot.Elem().NumField()-1 {
if field.Type.Kind() == reflect.Slice && field.Type.Elem().Kind() == reflect.Uint8 {
data, err := io.ReadAll(r)
if err != nil {
return err
}
fieldValue.SetBytes(data)
return nil
}
}
if err := binary.Read(r, binary.LittleEndian, fieldValue.Addr().Interface()); err != nil {
return err
}
}
if statusField := ov.FieldByName("Status"); !statusField.IsZero() {
code := statusField.Interface().(int8)
if code != 0x01 {
return Error{code}
}
}
return nil
}
-61
View File
@@ -1,61 +0,0 @@
package fusefs
import (
"go.arsenm.dev/infinitime"
"context"
"strconv"
)
func converterU8(ctx context.Context, in <-chan uint8) <-chan []byte {
out := make(chan []byte, 2)
go func() {
for {
select {
case <- ctx.Done():
return
case event := <-in:
out <- []byte(strconv.Itoa(int(event)) + "\n")
}
}
}()
return out
}
func converterU32(ctx context.Context, in <-chan uint32) <-chan []byte {
out := make(chan []byte, 2)
go func() {
for {
select {
case <- ctx.Done():
return
case event := <-in:
out <- []byte(strconv.Itoa(int(event)) + "\n")
}
}
}()
return out
}
func converterMotionValues(ctx context.Context, in <-chan infinitime.MotionValues) <-chan []byte {
out := make(chan []byte, 2)
go func() {
for {
select {
case <- ctx.Done():
return
case event := <-in:
out <- []byte(strconv.Itoa(int(event.X)) + " " + strconv.Itoa(int(event.Y)) + " " + strconv.Itoa(int(event.Z)) + "\n")
}
}
}()
return out
}
func converter1String(ctx context.Context, in string) <-chan []byte {
out := make(chan []byte, 2)
out <- []byte(in + "\n")
close(out)
return out
}
+108 -79
View File
@@ -1,24 +1,24 @@
package fusefs package fusefs
import ( import (
"go.arsenm.dev/infinitime" "bytes"
"go.arsenm.dev/infinitime/blefs"
"go.arsenm.dev/logger/log"
"context" "context"
"io"
"strconv"
"syscall" "syscall"
"github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"io" "go.elara.ws/itd/infinitime"
"bytes" "go.elara.ws/logger/log"
) )
type ITProperty struct { type ITProperty struct {
name string name string
Ino uint64 Ino uint64
gen func(context.Context) (<-chan []byte, error) gen func() ([]byte, error)
} }
type DirEntry struct { type DirEntry struct {
isDir bool isDir bool
modtime uint64 modtime uint64
@@ -28,7 +28,7 @@ type DirEntry struct {
type ITNode struct { type ITNode struct {
fs.Inode fs.Inode
kind int kind nodeKind
Ino uint64 Ino uint64
lst []DirEntry lst []DirEntry
@@ -36,57 +36,78 @@ type ITNode struct {
path string path string
} }
var myfs *blefs.FS = nil type nodeKind uint8
var inodemap map[string]uint64 = nil
const (
nodeKindRoot = iota
nodeKindInfo
nodeKindFS
nodeKindReadOnly
)
var (
myfs *infinitime.FS = nil
inodemap map[string]uint64 = nil
)
func BuildRootNode(dev *infinitime.Device) (*ITNode, error) { func BuildRootNode(dev *infinitime.Device) (*ITNode, error) {
var err error var err error
inodemap = make(map[string]uint64) inodemap = make(map[string]uint64)
myfs, err = dev.FS() myfs = dev.FS()
if err != nil { if err != nil {
log.Error("FUSE Failed to get filesystem").Err(err).Send() log.Error("FUSE Failed to get filesystem").Err(err).Send()
return nil, err return nil, err
} }
return &ITNode{kind: 0}, nil return &ITNode{kind: nodeKindRoot}, nil
} }
var properties = make([]ITProperty, 6) var properties = make([]ITProperty, 6)
func BuildProperties(dev *infinitime.Device) { func BuildProperties(dev *infinitime.Device) {
properties[0] = ITProperty{"heartrate", 2, properties[0] = ITProperty{
func(ctx context.Context) (<-chan []byte, error) { "heartrate", 2,
ans, err := dev.WatchHeartRate(ctx) func() ([]byte, error) {
return converterU8(ctx, ans), err ans, err := dev.HeartRate()
}} return []byte(strconv.Itoa(int(ans)) + "\n"), err
properties[1] = ITProperty{"battery", 3, },
func(ctx context.Context) (<-chan []byte, error) { }
ans, err := dev.WatchBatteryLevel(ctx) properties[1] = ITProperty{
return converterU8(ctx, ans), err "battery", 3,
}} func() ([]byte, error) {
properties[2] = ITProperty{"motion", 4, ans, err := dev.BatteryLevel()
func(ctx context.Context) (<-chan []byte, error) { return []byte(strconv.Itoa(int(ans)) + "\n"), err
ans, err := dev.WatchMotion(ctx) },
return converterMotionValues(ctx, ans), err }
}} properties[2] = ITProperty{
properties[3] = ITProperty{"stepcount", 5, "motion", 4,
func(ctx context.Context) (<-chan []byte, error) { func() ([]byte, error) {
ans, err := dev.WatchStepCount(ctx) ans, err := dev.Motion()
return converterU32(ctx, ans), err return []byte(strconv.Itoa(int(ans.X)) + " " + strconv.Itoa(int(ans.Y)) + " " + strconv.Itoa(int(ans.Z)) + "\n"), err
}} },
properties[4] = ITProperty{"version", 6, }
func(ctx context.Context) (<-chan []byte, error) { properties[3] = ITProperty{
ans, err := dev.Version() "stepcount", 6,
return converter1String(ctx, ans), err func() ([]byte, error) {
}} ans, err := dev.StepCount()
properties[5] = ITProperty{"address", 7, return []byte(strconv.Itoa(int(ans)) + "\n"), err
func(ctx context.Context) (<-chan []byte, error) { },
ans := dev.Address() }
return converter1String(ctx, ans), nil properties[4] = ITProperty{
}} "version", 7,
func() ([]byte, error) {
ans, err := dev.Version()
return []byte(ans + "\n"), err
},
}
properties[5] = ITProperty{
"address", 8,
func() ([]byte, error) {
ans := dev.Address()
return []byte(ans + "\n"), nil
},
}
} }
var _ fs.NodeReaddirer = (*ITNode)(nil) var _ fs.NodeReaddirer = (*ITNode)(nil)
@@ -97,7 +118,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
// root folder // root folder
r := make([]fuse.DirEntry, 2) r := make([]fuse.DirEntry, 2)
r[0] = fuse.DirEntry{ r[0] = fuse.DirEntry{
Name: "device", Name: "info",
Ino: 0, Ino: 0,
Mode: fuse.S_IFDIR, Mode: fuse.S_IFDIR,
} }
@@ -109,7 +130,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
return fs.NewListDirStream(r), 0 return fs.NewListDirStream(r), 0
case 1: case 1:
// device folder // info folder
r := make([]fuse.DirEntry, 6) r := make([]fuse.DirEntry, 6)
for ind, value := range properties { for ind, value := range properties {
r[ind] = fuse.DirEntry{ r[ind] = fuse.DirEntry{
@@ -122,7 +143,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
return fs.NewListDirStream(r), 0 return fs.NewListDirStream(r), 0
case 2: case 2:
// on device // on info
files, err := myfs.ReadDir(n.path) files, err := myfs.ReadDir(n.path)
if err != nil { if err != nil {
log.Error("FUSE ReadDir failed").Str("path", n.path).Err(err).Send() log.Error("FUSE ReadDir failed").Str("path", n.path).Err(err).Send()
@@ -175,16 +196,17 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
} }
var _ fs.NodeLookuper = (*ITNode)(nil) var _ fs.NodeLookuper = (*ITNode)(nil)
func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
switch n.kind { switch n.kind {
case 0: case 0:
// root folder // root folder
if name == "device" { if name == "info" {
stable := fs.StableAttr{ stable := fs.StableAttr{
Mode: fuse.S_IFDIR, Mode: fuse.S_IFDIR,
Ino: uint64(0), Ino: uint64(0),
} }
operations := &ITNode{kind: 1, Ino: 0} operations := &ITNode{kind: nodeKindInfo, Ino: 0}
child := n.NewInode(ctx, operations, stable) child := n.NewInode(ctx, operations, stable)
return child, 0 return child, 0
} else if name == "fs" { } else if name == "fs" {
@@ -192,19 +214,19 @@ func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*
Mode: fuse.S_IFDIR, Mode: fuse.S_IFDIR,
Ino: uint64(1), Ino: uint64(1),
} }
operations := &ITNode{kind: 2, Ino: 1, path : ""} operations := &ITNode{kind: nodeKindFS, Ino: 1, path: ""}
child := n.NewInode(ctx, operations, stable) child := n.NewInode(ctx, operations, stable)
return child, 0 return child, 0
} }
case 1: case 1:
// device folder // info folder
for _, value := range properties { for _, value := range properties {
if value.name == name { if value.name == name {
stable := fs.StableAttr{ stable := fs.StableAttr{
Mode: fuse.S_IFREG, Mode: fuse.S_IFREG,
Ino: uint64(value.Ino), Ino: uint64(value.Ino),
} }
operations := &ITNode{kind: 3, Ino: value.Ino} operations := &ITNode{kind: nodeKindReadOnly, Ino: value.Ino}
child := n.NewInode(ctx, operations, stable) child := n.NewInode(ctx, operations, stable)
return child, 0 return child, 0
} }
@@ -227,7 +249,7 @@ func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*
Mode: fuse.S_IFDIR, Mode: fuse.S_IFDIR,
Ino: inodemap[file.path], Ino: inodemap[file.path],
} }
operations := &ITNode{kind: 2, path: file.path} operations := &ITNode{kind: nodeKindFS, path: file.path}
child := n.NewInode(ctx, operations, stable) child := n.NewInode(ctx, operations, stable)
return child, 0 return child, 0
} else { } else {
@@ -236,13 +258,12 @@ func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*
Ino: inodemap[file.path], Ino: inodemap[file.path],
} }
operations := &ITNode{ operations := &ITNode{
kind: 2, path: file.path, kind: nodeKindFS, path: file.path,
self: file, self: file,
} }
child := n.NewInode(ctx, operations, stable) child := n.NewInode(ctx, operations, stable)
return child, 0 return child, 0
} }
break
} }
log.Warn("FUSE Lookup failed").Str("path", n.path+"/"+name).Send() log.Warn("FUSE Lookup failed").Str("path", n.path+"/"+name).Send()
} }
@@ -254,6 +275,7 @@ type bytesFileReadHandle struct {
} }
var _ fs.FileReader = (*bytesFileReadHandle)(nil) var _ fs.FileReader = (*bytesFileReadHandle)(nil)
func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send() log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
end := off + int64(len(dest)) end := off + int64(len(dest))
@@ -264,30 +286,35 @@ func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64)
} }
type sensorFileReadHandle struct { type sensorFileReadHandle struct {
ch <-chan []byte content []byte
cancel context.CancelFunc
} }
var _ fs.FileReader = (*sensorFileReadHandle)(nil) var _ fs.FileReader = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { func (fh *sensorFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
content := <-fh.ch log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
return fuse.ReadResultData(content), 0 end := off + int64(len(dest))
if end > int64(len(fh.content)) {
end = int64(len(fh.content))
}
return fuse.ReadResultData(fh.content[off:end]), 0
} }
var _ fs.FileFlusher = (*sensorFileReadHandle)(nil) var _ fs.FileFlusher = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Flush(ctx context.Context) (errno syscall.Errno) { func (fh *sensorFileReadHandle) Flush(ctx context.Context) (errno syscall.Errno) {
fh.cancel()
return 0 return 0
} }
type bytesFileWriteHandle struct { type bytesFileWriteHandle struct {
content []byte content []byte
path string path string
} }
var _ fs.FileWriter = (*bytesFileWriteHandle)(nil) var _ fs.FileWriter = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) { func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
log.Info("Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send() log.Debug("FUSE Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send()
if off != int64(len(fh.content)) { if off != int64(len(fh.content)) {
log.Error("FUSE Write file size changed unexpectedly").Int("expect", int(off)).Int("received", len(fh.content)).Send() log.Error("FUSE Write file size changed unexpectedly").Int("expect", int(off)).Int("received", len(fh.content)).Send()
return 0, syscall.ENXIO return 0, syscall.ENXIO
@@ -297,8 +324,8 @@ func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int6
} }
var _ fs.FileFlusher = (*bytesFileWriteHandle)(nil) var _ fs.FileFlusher = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno) {
func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno) {
log.Debug("FUSE Attempting flush").Str("path", fh.path).Send() log.Debug("FUSE Attempting flush").Str("path", fh.path).Send()
fp, err := myfs.Create(fh.path, uint32(len(fh.content))) fp, err := myfs.Create(fh.path, uint32(len(fh.content)))
if err != nil { if err != nil {
@@ -316,12 +343,9 @@ func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno)
return 0 return 0
} }
go func() { fp.ProgressFunc = func(transferred, total uint32) {
// For every progress event log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
for sent := range fp.Progress() {
log.Debug("FUSE Flush progress").Int("bytes", int(sent)).Int("total", len(fh.content)).Send()
} }
}()
r := bytes.NewReader(fh.content) r := bytes.NewReader(fh.content)
nread, err := io.Copy(fp, r) nread, err := io.Copy(fp, r)
@@ -344,12 +368,15 @@ func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno)
return 0 return 0
} }
var _ fs.FileFsyncer = (*bytesFileWriteHandle)(nil) var _ fs.FileFsyncer = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { func (fh *bytesFileWriteHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
return fh.Flush(ctx) return fh.Flush(ctx)
} }
var _ fs.NodeGetattrer = (*ITNode)(nil) var _ fs.NodeGetattrer = (*ITNode)(nil)
func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
log.Debug("FUSE getattr").Str("path", bn.path).Send() log.Debug("FUSE getattr").Str("path", bn.path).Send()
out.Ino = bn.Ino out.Ino = bn.Ino
@@ -361,6 +388,7 @@ func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOu
} }
var _ fs.NodeSetattrer = (*ITNode)(nil) var _ fs.NodeSetattrer = (*ITNode)(nil)
func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
log.Debug("FUSE setattr").Str("path", bn.path).Send() log.Debug("FUSE setattr").Str("path", bn.path).Send()
out.Size = 0 out.Size = 0
@@ -369,6 +397,7 @@ func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAtt
} }
var _ fs.NodeOpener = (*ITNode)(nil) var _ fs.NodeOpener = (*ITNode)(nil)
func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
switch f.kind { switch f.kind {
case 2: case 2:
@@ -397,12 +426,9 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
b := &bytes.Buffer{} b := &bytes.Buffer{}
go func() { fp.ProgressFunc = func(transferred, total uint32) {
// For every progress event log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
for sent := range fp.Progress() {
log.Debug("FUSE Read progress").Int("bytes", int(sent)).Int("total", int(f.self.size)).Send()
} }
}()
_, err = io.Copy(b, fp) _, err = io.Copy(b, fp)
if err != nil { if err != nil {
@@ -427,15 +453,13 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
for _, value := range properties { for _, value := range properties {
if value.Ino == f.Ino { if value.Ino == f.Ino {
sub_ctx, cancel := context.WithCancel(ctx) ans, err := value.gen()
ans, err := value.gen(sub_ctx)
if err != nil { if err != nil {
return nil, 0, syscallErr(err) return nil, 0, syscallErr(err)
} }
fh = &sensorFileReadHandle{ fh = &sensorFileReadHandle{
ch: ans, content: ans,
cancel : cancel,
} }
return fh, fuse.FOPEN_DIRECT_IO, 0 return fh, fuse.FOPEN_DIRECT_IO, 0
} }
@@ -445,6 +469,7 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
} }
var _ fs.NodeCreater = (*ITNode)(nil) var _ fs.NodeCreater = (*ITNode)(nil)
func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
if f.kind != 2 { if f.kind != 2 {
return nil, nil, 0, syscall.EROFS return nil, nil, 0, syscall.EROFS
@@ -459,7 +484,7 @@ func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uin
Ino: ino, Ino: ino,
} }
operations := &ITNode{ operations := &ITNode{
kind: 2, Ino: ino, kind: nodeKindFS, Ino: ino,
path: path, path: path,
} }
node = f.NewInode(ctx, operations, stable) node = f.NewInode(ctx, operations, stable)
@@ -476,6 +501,7 @@ func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uin
} }
var _ fs.NodeMkdirer = (*ITNode)(nil) var _ fs.NodeMkdirer = (*ITNode)(nil)
func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
if f.kind != 2 { if f.kind != 2 {
return nil, syscall.EROFS return nil, syscall.EROFS
@@ -499,7 +525,7 @@ func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.
Ino: ino, Ino: ino,
} }
operations := &ITNode{ operations := &ITNode{
kind: 2, Ino: ino, kind: nodeKindFS, Ino: ino,
path: path, path: path,
} }
node := f.NewInode(ctx, operations, stable) node := f.NewInode(ctx, operations, stable)
@@ -512,6 +538,7 @@ func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.
} }
var _ fs.NodeRenamer = (*ITNode)(nil) var _ fs.NodeRenamer = (*ITNode)(nil)
func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno { func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
if f.kind != 2 { if f.kind != 2 {
return syscall.EROFS return syscall.EROFS
@@ -543,6 +570,7 @@ func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbe
} }
var _ fs.NodeUnlinker = (*ITNode)(nil) var _ fs.NodeUnlinker = (*ITNode)(nil)
func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno { func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
if f.kind != 2 { if f.kind != 2 {
return syscall.EROFS return syscall.EROFS
@@ -566,6 +594,7 @@ func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
} }
var _ fs.NodeRmdirer = (*ITNode)(nil) var _ fs.NodeRmdirer = (*ITNode)(nil)
func (f *ITNode) Rmdir(ctx context.Context, name string) syscall.Errno { func (f *ITNode) Rmdir(ctx context.Context, name string) syscall.Errno {
return f.Unlink(ctx, name) return f.Unlink(ctx, name)
} }
+44 -39
View File
@@ -1,7 +1,9 @@
package fusefs package fusefs
import ( import (
"go.arsenm.dev/infinitime/blefs"
"syscall" "syscall"
"go.elara.ws/itd/internal/fsproto"
) )
func syscallErr(err error) syscall.Errno { func syscallErr(err error) syscall.Errno {
@@ -9,59 +11,62 @@ func syscallErr(err error) syscall.Errno {
return 0 return 0
} }
switch err { switch err := err.(type) {
case blefs.FSError{0x02}: // filesystem error case fsproto.Error:
return syscall.EIO // TODO switch err.Code {
case blefs.FSError{0x05}: // read-only filesystem case 0x02: // filesystem error
return syscall.EROFS
case blefs.FSError{0x03}: // no such file
return syscall.ENOENT
case blefs.FSError{0x04}: // protocol error
return syscall.EPROTO
case blefs.FSError{-5}: // input/output error
return syscall.EIO return syscall.EIO
case blefs.FSError{-84}: // filesystem is corrupted case 0x05: // read-only filesystem
return syscall.ENOTRECOVERABLE // TODO return syscall.EROFS
case blefs.FSError{-2}: // no such directory entry case 0x03: // no such file
return syscall.ENOENT return syscall.ENOENT
case blefs.FSError{-17}: // entry already exists case 0x04: // protocol error
return syscall.EPROTO
case -5: // input/output error
return syscall.EIO
case -84: // filesystem is corrupted
return syscall.ENOTRECOVERABLE
case -2: // no such directory entry
return syscall.ENOENT
case -17: // entry already exists
return syscall.EEXIST return syscall.EEXIST
case blefs.FSError{-20}: // entry is not a directory case -20: // entry is not a directory
return syscall.ENOTDIR return syscall.ENOTDIR
case blefs.FSError{-39}: // directory is not empty case -39: // directory is not empty
return syscall.ENOTEMPTY return syscall.ENOTEMPTY
case blefs.FSError{-9}: // bad file number case -9: // bad file number
return syscall.EBADF return syscall.EBADF
case blefs.FSError{-27}: // file is too large case -27: // file is too large
return syscall.EFBIG return syscall.EFBIG
case blefs.FSError{-22}: // invalid parameter case -22: // invalid parameter
return syscall.EINVAL return syscall.EINVAL
case blefs.FSError{-28}: // no space left on device case -28: // no space left on device
return syscall.ENOSPC return syscall.ENOSPC
case blefs.FSError{-12}: // no more memory available case -12: // no more memory available
return syscall.ENOMEM return syscall.ENOMEM
case blefs.FSError{-61}: // no attr available case -61: // no attr available
return syscall.ENODATA // TODO return syscall.ENODATA
case blefs.FSError{-36}: // file name is too long case -36: // file name is too long
return syscall.ENAMETOOLONG return syscall.ENAMETOOLONG
case blefs.ErrFileNotExists: // file does not exist }
default:
switch err {
case fsproto.ErrFileNotExists: // file does not exist
return syscall.ENOENT return syscall.ENOENT
case blefs.ErrFileReadOnly: // file is read only case fsproto.ErrFileReadOnly: // file is read only
return syscall.EACCES return syscall.EACCES
case blefs.ErrFileWriteOnly: // file is write only case fsproto.ErrFileWriteOnly: // file is write only
return syscall.EACCES return syscall.EACCES
case blefs.ErrInvalidOffset: // invalid file offset case fsproto.ErrInvalidOffset: // invalid file offset
return syscall.EFAULT // TODO return syscall.EINVAL
case blefs.ErrOffsetChanged: // offset has already been changed case fsproto.ErrNoRemoveRoot: // refusing to remove root directory
return syscall.ESPIPE
case blefs.ErrReadOpen: // only one file can be opened for reading at a time
return syscall.ENFILE
case blefs.ErrWriteOpen: // only one file can be opened for writing at a time
return syscall.ENFILE
case blefs.ErrNoRemoveRoot: // refusing to remove root directory
return syscall.EPERM return syscall.EPERM
case fsproto.ErrFileClosed: // cannot perform operation on closed file
return syscall.EBADF
default:
return syscall.EINVAL
}
} }
return syscall.EIO // TODO return syscall.EIO
} }
+17
View File
@@ -0,0 +1,17 @@
package fusefs
import (
_ "unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
func Unmount(mountPoint string) error {
return unmount(mountPoint, &fuse.MountOptions{DirectMount: false})
}
// Unfortunately, the FUSE library does not export its unmount function,
// so this is required until that changes
//
//go:linkname unmount github.com/hanwen/go-fuse/v2/fuse.unmount
func unmount(mountPoint string, opts *fuse.MountOptions) error
+58 -52
View File
@@ -26,15 +26,16 @@ import (
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
"sync"
"syscall" "syscall"
"time" "time"
"github.com/gen2brain/dlgs" "github.com/gen2brain/dlgs"
"github.com/knadh/koanf" "github.com/knadh/koanf"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/logger" "go.elara.ws/logger"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
var k = koanf.New(".") var k = koanf.New(".")
@@ -58,46 +59,11 @@ func main() {
if err != nil { if err != nil {
level = logger.LogLevelInfo level = logger.LogLevelInfo
} }
log.Logger.SetLevel(level)
// Initialize infinitime library
infinitime.Init(k.String("bluetooth.adapter"))
// Cleanly exit after function
defer infinitime.Exit()
// Create infinitime options struct // Create infinitime options struct
opts := &infinitime.Options{ opts := infinitime.Options{
AttemptReconnect: k.Bool("conn.reconnect"), OnReconnect: func(dev *infinitime.Device) {
WhitelistEnabled: k.Bool("conn.whitelist.enabled"),
Whitelist: k.Strings("conn.whitelist.devices"),
OnReqPasskey: onReqPasskey,
Logger: log.Logger,
LogLevel: level,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
go func() {
<-sigCh
cancel()
time.Sleep(200 * time.Millisecond)
os.Exit(0)
}()
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
)
// Connect to InfiniTime with default options
dev, err := infinitime.Connect(ctx, opts)
if err != nil {
log.Fatal("Error connecting to InfiniTime").Err(err).Send()
}
// When InfiniTime reconnects
opts.OnReconnect = func() {
if k.Bool("on.reconnect.setTime") { if k.Bool("on.reconnect.setTime") {
// Set time to current time // Set time to current time
err = dev.SetTime(time.Now()) err = dev.SetTime(time.Now())
@@ -119,6 +85,15 @@ func main() {
updateFS = true updateFS = true
// Resend weather on reconnect // Resend weather on reconnect
sendWeatherCh <- struct{}{} sendWeatherCh <- struct{}{}
},
}
ctx := context.Background()
// Connect to InfiniTime with default options
dev, err := infinitime.Connect(opts)
if err != nil {
log.Fatal("Error connecting to InfiniTime").Err(err).Send()
} }
// Get firmware version // Get firmware version
@@ -145,57 +120,88 @@ func main() {
log.Error("Error setting current time on connected InfiniTime").Err(err).Send() log.Error("Error setting current time on connected InfiniTime").Err(err).Send()
} }
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
sig := <-sigCh
log.Warn("Signal received, shutting down").Stringer("signal", sig).Send()
cancel()
}()
wg := WaitGroup{&sync.WaitGroup{}}
// Initialize music controls // Initialize music controls
err = initMusicCtrl(ctx, dev) err = initMusicCtrl(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error initializing music control").Err(err).Send() log.Error("Error initializing music control").Err(err).Send()
} }
// Start control socket // Start control socket
err = initCallNotifs(ctx, dev) err = initCallNotifs(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error initializing call notifications").Err(err).Send() log.Error("Error initializing call notifications").Err(err).Send()
} }
// Initialize notification relay // Initialize notification relay
err = initNotifRelay(ctx, dev) err = initNotifRelay(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error initializing notification relay").Err(err).Send() log.Error("Error initializing notification relay").Err(err).Send()
} }
// Initializa weather // Initializa weather
err = initWeather(ctx, dev) err = initWeather(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error initializing weather").Err(err).Send() log.Error("Error initializing weather").Err(err).Send()
} }
// Initialize metrics collection // Initialize metrics collection
err = initMetrics(ctx, dev) err = initMetrics(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error intializing metrics collection").Err(err).Send() log.Error("Error intializing metrics collection").Err(err).Send()
} }
// Initialize metrics collection // Initialize puremaps integration
err = initPureMaps(ctx, dev) err = initPureMaps(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error intializing puremaps integration").Err(err).Send() log.Error("Error intializing puremaps integration").Err(err).Send()
} }
// Start fuse socket // Start fuse socket
if k.Bool("fuse.enabled") { if k.Bool("fuse.enabled") {
err = startFUSE(ctx, dev) err = startFUSE(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error starting fuse socket").Err(err).Send() log.Error("Error starting fuse socket").Err(err).Send()
} }
} }
// Start control socket // Start control socket
err = startSocket(ctx, dev) err = startSocket(ctx, wg, dev)
if err != nil { if err != nil {
log.Error("Error starting socket").Err(err).Send() log.Error("Error starting socket").Err(err).Send()
} }
// Block forever
select {} wg.Wait()
}
type x struct {
n int
*sync.WaitGroup
}
func (xy *x) Add(i int) {
xy.n += i
xy.WaitGroup.Add(i)
fmt.Println("add: counter:", xy.n)
}
func (xy *x) Done() {
xy.n -= 1
xy.WaitGroup.Done()
fmt.Println("done: counter:", xy.n)
} }
func onReqPasskey() (uint32, error) { func onReqPasskey() (uint32, error) {
+15 -12
View File
@@ -5,9 +5,9 @@ import (
"strings" "strings"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/itd/internal/utils" "go.elara.ws/itd/internal/utils"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
const ( const (
@@ -18,7 +18,7 @@ const (
progressProperty = interfaceName + ".progress" progressProperty = interfaceName + ".progress"
) )
func initPureMaps(ctx context.Context, dev *infinitime.Device) error { func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// Connect to session bus. This connection is for method calls. // Connect to session bus. This connection is for method calls.
conn, err := utils.NewSessionBusConn(ctx) conn, err := utils.NewSessionBusConn(ctx)
if err != nil { if err != nil {
@@ -59,7 +59,10 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
} }
} }
wg.Add(1)
go func() { go func() {
defer wg.Done("pureMaps")
signalCh := make(chan *dbus.Message, 10) signalCh := make(chan *dbus.Message, 10)
monitorConn.Eavesdrop(signalCh) monitorConn.Eavesdrop(signalCh)
@@ -97,7 +100,7 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
continue continue
} }
err = dev.Navigation.SetFlag(infinitime.NavFlag(icon)) err = dev.SetNavFlag(infinitime.NavFlag(icon))
if err != nil { if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send() log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue continue
@@ -110,7 +113,7 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
continue continue
} }
err = dev.Navigation.SetNarrative(narrative) err = dev.SetNavNarrative(narrative)
if err != nil { if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send() log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue continue
@@ -123,7 +126,7 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
continue continue
} }
err = dev.Navigation.SetManDist(manDist) err = dev.SetNavManeuverDistance(manDist)
if err != nil { if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send() log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue continue
@@ -136,7 +139,7 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
continue continue
} }
err = dev.Navigation.SetProgress(uint8(progress)) err = dev.SetNavProgress(uint8(progress))
if err != nil { if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send() log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue continue
@@ -162,7 +165,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err return err
} }
err = dev.Navigation.SetFlag(infinitime.NavFlag(icon)) err = dev.SetNavFlag(infinitime.NavFlag(icon))
if err != nil { if err != nil {
return err return err
} }
@@ -173,7 +176,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err return err
} }
err = dev.Navigation.SetNarrative(narrative) err = dev.SetNavNarrative(narrative)
if err != nil { if err != nil {
return err return err
} }
@@ -184,7 +187,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err return err
} }
err = dev.Navigation.SetManDist(manDist) err = dev.SetNavManeuverDistance(manDist)
if err != nil { if err != nil {
return err return err
} }
@@ -195,7 +198,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err return err
} }
return dev.Navigation.SetProgress(uint8(progress)) return dev.SetNavProgress(uint8(progress))
} }
// pureMapsExists checks to make sure the PureMaps service exists on the bus // pureMapsExists checks to make sure the PureMaps service exists on the bus
+60 -40
View File
@@ -6,12 +6,12 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
func initMetrics(ctx context.Context, dev *infinitime.Device) error { func initMetrics(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// If metrics disabled, return nil // If metrics disabled, return nil
if !k.Bool("metrics.enabled") { if !k.Bool("metrics.enabled") {
return nil return nil
@@ -47,70 +47,81 @@ func initMetrics(ctx context.Context, dev *infinitime.Device) error {
return err return err
} }
// If heart rate metrics enabled in config
if k.Bool("metrics.heartRate.enabled") {
// Watch heart rate // Watch heart rate
heartRateCh, err := dev.WatchHeartRate(ctx) if k.Bool("metrics.heartRate.enabled") {
err := dev.WatchHeartRate(ctx, func(heartRate uint8, err error) {
if err != nil { if err != nil {
return err // Handle error
return
} }
go func() {
// For every heart rate sample
for heartRate := range heartRateCh {
// Get current time // Get current time
unixTime := time.Now().UnixNano() unixTime := time.Now().UnixNano()
// Insert sample and time into database // Insert sample and time into database
db.Exec("INSERT INTO heartRate VALUES (?, ?);", unixTime, heartRate) db.Exec("INSERT INTO heartRate VALUES (?, ?);", unixTime, heartRate)
})
if err != nil {
return err
} }
}()
} }
// If step count metrics enabled in config // If step count metrics enabled in config
if k.Bool("metrics.stepCount.enabled") { if k.Bool("metrics.stepCount.enabled") {
// Watch step count // Watch step count
stepCountCh, err := dev.WatchStepCount(ctx) err := dev.WatchStepCount(ctx, func(count uint32, err error) {
if err != nil { if err != nil {
return err return
} }
go func() {
// For every step count sample
for stepCount := range stepCountCh {
// Get current time // Get current time
unixTime := time.Now().UnixNano() unixTime := time.Now().UnixNano()
// Insert sample and time into database // Insert sample and time into database
db.Exec("INSERT INTO stepCount VALUES (?, ?);", unixTime, stepCount) db.Exec("INSERT INTO stepCount VALUES (?, ?);", unixTime, count)
} })
}()
}
// If battery level metrics enabled in config
if k.Bool("metrics.battLevel.enabled") {
// Watch battery level
battLevelCh, err := dev.WatchBatteryLevel(ctx)
if err != nil { if err != nil {
return err return err
} }
go func() { }
// For every battery level sample
for battLevel := range battLevelCh { // Watch step count
if k.Bool("metrics.stepCount.enabled") {
err := dev.WatchStepCount(ctx, func(count uint32, err error) {
if err != nil {
// Handle error
return
}
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample and time into database
db.Exec("INSERT INTO stepCount VALUES (?, ?);", unixTime, count)
})
if err != nil {
return err
}
}
// Watch battery level
if k.Bool("metrics.battLevel.enabled") {
err := dev.WatchBatteryLevel(ctx, func(battLevel uint8, err error) {
if err != nil {
// Handle error
return
}
// Get current time // Get current time
unixTime := time.Now().UnixNano() unixTime := time.Now().UnixNano()
// Insert sample and time into database // Insert sample and time into database
db.Exec("INSERT INTO battLevel VALUES (?, ?);", unixTime, battLevel) db.Exec("INSERT INTO battLevel VALUES (?, ?);", unixTime, battLevel)
} })
}()
}
// If motion metrics enabled in config
if k.Bool("metrics.motion.enabled") {
// Watch motion values
motionCh, err := dev.WatchMotion(ctx)
if err != nil { if err != nil {
return err return err
} }
go func() { }
// For every motion sample
for motionVals := range motionCh { // Watch motion values
if k.Bool("metrics.motion.enabled") {
err := dev.WatchMotion(ctx, func(motionVals infinitime.MotionValues, err error) {
if err != nil {
// Handle error
return
}
// Get current time // Get current time
unixTime := time.Now().UnixNano() unixTime := time.Now().UnixNano()
// Insert sample values and time into database // Insert sample values and time into database
@@ -121,9 +132,18 @@ func initMetrics(ctx context.Context, dev *infinitime.Device) error {
motionVals.Y, motionVals.Y,
motionVals.Z, motionVals.Z,
) )
})
if err != nil {
return err
} }
}
wg.Add(1)
go func() {
defer wg.Done("metrics")
<-ctx.Done()
db.Close()
}() }()
}
log.Info("Initialized metrics collection").Send() log.Info("Initialized metrics collection").Send()
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"sync" "sync"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"go.arsenm.dev/itd/internal/utils" "go.elara.ws/itd/internal/utils"
) )
var ( var (
+16 -16
View File
@@ -21,13 +21,13 @@ package main
import ( import (
"context" "context"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/itd/mpris" "go.elara.ws/itd/mpris"
"go.arsenm.dev/itd/translit" "go.elara.ws/itd/translit"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
func initMusicCtrl(ctx context.Context, dev *infinitime.Device) error { func initMusicCtrl(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
mpris.Init(ctx) mpris.Init(ctx)
maps := k.Strings("notifs.translit.use") maps := k.Strings("notifs.translit.use")
@@ -38,27 +38,25 @@ func initMusicCtrl(ctx context.Context, dev *infinitime.Device) error {
if !firmwareUpdating { if !firmwareUpdating {
switch ct { switch ct {
case mpris.ChangeTypeStatus: case mpris.ChangeTypeStatus:
dev.Music.SetStatus(val == "Playing") dev.SetMusicStatus(val == "Playing")
case mpris.ChangeTypeTitle: case mpris.ChangeTypeTitle:
dev.Music.SetTrack(newVal) dev.SetMusicTrack(newVal)
case mpris.ChangeTypeAlbum: case mpris.ChangeTypeAlbum:
dev.Music.SetAlbum(newVal) dev.SetMusicAlbum(newVal)
case mpris.ChangeTypeArtist: case mpris.ChangeTypeArtist:
dev.Music.SetArtist(newVal) dev.SetMusicArtist(newVal)
} }
} }
}) })
// Watch for music events // Watch for music events
musicEvtCh, err := dev.Music.WatchEvents() err := dev.WatchMusicEvents(ctx, func(event infinitime.MusicEvent, err error) {
if err != nil { if err != nil {
return err log.Error("Music event error").Err(err).Send()
} }
go func() {
// For every music event received
for musicEvt := range musicEvtCh {
// Perform appropriate action based on event // Perform appropriate action based on event
switch musicEvt { switch event {
case infinitime.MusicEventPlay: case infinitime.MusicEventPlay:
mpris.Play() mpris.Play()
case infinitime.MusicEventPause: case infinitime.MusicEventPause:
@@ -72,8 +70,10 @@ func initMusicCtrl(ctx context.Context, dev *infinitime.Device) error {
case infinitime.MusicEventVolDown: case infinitime.MusicEventVolDown:
mpris.VolDown(uint(k.Int("music.vol.interval"))) mpris.VolDown(uint(k.Int("music.vol.interval")))
} }
})
if err != nil {
return err
} }
}()
// Log completed initialization // Log completed initialization
log.Info("Initialized InfiniTime music controls").Send() log.Info("Initialized InfiniTime music controls").Send()
+14 -6
View File
@@ -23,13 +23,13 @@ import (
"fmt" "fmt"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/itd/internal/utils" "go.elara.ws/itd/internal/utils"
"go.arsenm.dev/itd/translit" "go.elara.ws/itd/translit"
"go.arsenm.dev/logger/log" "go.elara.ws/logger/log"
) )
func initNotifRelay(ctx context.Context, dev *infinitime.Device) error { func initNotifRelay(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// Connect to dbus session bus // Connect to dbus session bus
bus, err := utils.NewSessionBusConn(ctx) bus, err := utils.NewSessionBusConn(ctx)
if err != nil { if err != nil {
@@ -54,9 +54,13 @@ func initNotifRelay(ctx context.Context, dev *infinitime.Device) error {
// Send events to channel // Send events to channel
bus.Eavesdrop(notifCh) bus.Eavesdrop(notifCh)
wg.Add(1)
go func() { go func() {
defer wg.Done("notifRelay")
// For every event sent to channel // For every event sent to channel
for v := range notifCh { for {
select {
case v := <-notifCh:
// If firmware is updating, skip // If firmware is updating, skip
if firmwareUpdating { if firmwareUpdating {
continue continue
@@ -91,6 +95,10 @@ func initNotifRelay(ctx context.Context, dev *infinitime.Device) error {
} }
dev.Notify(sender, msg) dev.Notify(sender, msg)
case <-ctx.Done():
bus.Close()
return
}
} }
}() }()
+189 -132
View File
@@ -19,6 +19,7 @@
package main package main
import ( import (
"archive/zip"
"context" "context"
"errors" "errors"
"io" "io"
@@ -27,11 +28,10 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"go.arsenm.dev/drpc/muxserver" "go.elara.ws/drpc/muxserver"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/infinitime/blefs" "go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc" "go.elara.ws/logger/log"
"go.arsenm.dev/logger/log"
"storj.io/drpc/drpcmux" "storj.io/drpc/drpcmux"
) )
@@ -41,7 +41,7 @@ var (
ErrDFUInvalidUpgType = errors.New("invalid upgrade type") ErrDFUInvalidUpgType = errors.New("invalid upgrade type")
) )
func startSocket(ctx context.Context, dev *infinitime.Device) error { func startSocket(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
// Make socket directory if non-existant // Make socket directory if non-existant
err := os.MkdirAll(filepath.Dir(k.String("socket.path")), 0o755) err := os.MkdirAll(filepath.Dir(k.String("socket.path")), 0o755)
if err != nil { if err != nil {
@@ -60,11 +60,7 @@ func startSocket(ctx context.Context, dev *infinitime.Device) error {
return err return err
} }
fs, err := dev.FS() fs := dev.FS()
if err != nil {
log.Warn("Error getting BLE filesystem").Err(err).Send()
}
mux := drpcmux.New() mux := drpcmux.New()
err = rpc.DRPCRegisterITD(mux, &ITD{dev}) err = rpc.DRPCRegisterITD(mux, &ITD{dev})
@@ -77,10 +73,13 @@ func startSocket(ctx context.Context, dev *infinitime.Device) error {
return err return err
} }
go muxserver.New(mux).Serve(ctx, ln) log.Info("Starting control socket").Str("path", k.String("socket.path")).Send()
// Log socket start wg.Add(1)
log.Info("Started control socket").Str("path", k.String("socket.path")).Send() go func() {
defer wg.Done("socket")
muxserver.New(mux).Serve(ctx, ln)
}()
return nil return nil
} }
@@ -95,20 +94,30 @@ func (i *ITD) HeartRate(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, erro
} }
func (i *ITD) WatchHeartRate(_ *rpc.Empty, s rpc.DRPCITD_WatchHeartRateStream) error { func (i *ITD) WatchHeartRate(_ *rpc.Empty, s rpc.DRPCITD_WatchHeartRateStream) error {
heartRateCh, err := i.dev.WatchHeartRate(s.Context()) errCh := make(chan error)
err := i.dev.WatchHeartRate(s.Context(), func(rate uint8, err error) {
if err != nil {
errCh <- err
return
}
err = s.Send(&rpc.IntResponse{Value: uint32(rate)})
if err != nil {
errCh <- err
}
})
if err != nil { if err != nil {
return err return err
} }
for heartRate := range heartRateCh { select {
err = s.Send(&rpc.IntResponse{Value: uint32(heartRate)}) case <-errCh:
if err != nil {
return err return err
} case <-s.Context().Done():
}
return nil return nil
} }
}
func (i *ITD) BatteryLevel(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) { func (i *ITD) BatteryLevel(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) {
bl, err := i.dev.BatteryLevel() bl, err := i.dev.BatteryLevel()
@@ -116,20 +125,30 @@ func (i *ITD) BatteryLevel(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, e
} }
func (i *ITD) WatchBatteryLevel(_ *rpc.Empty, s rpc.DRPCITD_WatchBatteryLevelStream) error { func (i *ITD) WatchBatteryLevel(_ *rpc.Empty, s rpc.DRPCITD_WatchBatteryLevelStream) error {
battLevelCh, err := i.dev.WatchBatteryLevel(s.Context()) errCh := make(chan error)
err := i.dev.WatchBatteryLevel(s.Context(), func(level uint8, err error) {
if err != nil {
errCh <- err
return
}
err = s.Send(&rpc.IntResponse{Value: uint32(level)})
if err != nil {
errCh <- err
}
})
if err != nil { if err != nil {
return err return err
} }
for battLevel := range battLevelCh { select {
err = s.Send(&rpc.IntResponse{Value: uint32(battLevel)}) case <-errCh:
if err != nil {
return err return err
} case <-s.Context().Done():
}
return nil return nil
} }
}
func (i *ITD) Motion(_ context.Context, _ *rpc.Empty) (*rpc.MotionResponse, error) { func (i *ITD) Motion(_ context.Context, _ *rpc.Empty) (*rpc.MotionResponse, error) {
motionVals, err := i.dev.Motion() motionVals, err := i.dev.Motion()
@@ -141,24 +160,34 @@ func (i *ITD) Motion(_ context.Context, _ *rpc.Empty) (*rpc.MotionResponse, erro
} }
func (i *ITD) WatchMotion(_ *rpc.Empty, s rpc.DRPCITD_WatchMotionStream) error { func (i *ITD) WatchMotion(_ *rpc.Empty, s rpc.DRPCITD_WatchMotionStream) error {
motionValsCh, err := i.dev.WatchMotion(s.Context()) errCh := make(chan error)
err := i.dev.WatchMotion(s.Context(), func(motion infinitime.MotionValues, err error) {
if err != nil { if err != nil {
return err errCh <- err
return
} }
for motionVals := range motionValsCh {
err = s.Send(&rpc.MotionResponse{ err = s.Send(&rpc.MotionResponse{
X: int32(motionVals.X), X: int32(motion.X),
Y: int32(motionVals.Y), Y: int32(motion.Y),
Z: int32(motionVals.Z), Z: int32(motion.Z),
})
if err != nil {
errCh <- err
}
}) })
if err != nil { if err != nil {
return err return err
} }
}
select {
case <-errCh:
return err
case <-s.Context().Done():
return nil return nil
} }
}
func (i *ITD) StepCount(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) { func (i *ITD) StepCount(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) {
sc, err := i.dev.StepCount() sc, err := i.dev.StepCount()
@@ -166,20 +195,30 @@ func (i *ITD) StepCount(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, erro
} }
func (i *ITD) WatchStepCount(_ *rpc.Empty, s rpc.DRPCITD_WatchStepCountStream) error { func (i *ITD) WatchStepCount(_ *rpc.Empty, s rpc.DRPCITD_WatchStepCountStream) error {
stepCountCh, err := i.dev.WatchStepCount(s.Context()) errCh := make(chan error)
err := i.dev.WatchStepCount(s.Context(), func(count uint32, err error) {
if err != nil {
errCh <- err
return
}
err = s.Send(&rpc.IntResponse{Value: count})
if err != nil {
errCh <- err
}
})
if err != nil { if err != nil {
return err return err
} }
for stepCount := range stepCountCh { select {
err = s.Send(&rpc.IntResponse{Value: stepCount}) case <-errCh:
if err != nil {
return err return err
} case <-s.Context().Done():
}
return nil return nil
} }
}
func (i *ITD) Version(_ context.Context, _ *rpc.Empty) (*rpc.StringResponse, error) { func (i *ITD) Version(_ context.Context, _ *rpc.Empty) (*rpc.StringResponse, error) {
v, err := i.dev.Version() v, err := i.dev.Version()
@@ -203,39 +242,34 @@ func (i *ITD) WeatherUpdate(context.Context, *rpc.Empty) (*rpc.Empty, error) {
return &rpc.Empty{}, nil return &rpc.Empty{}, nil
} }
func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_FirmwareUpgradeStream) error { func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_FirmwareUpgradeStream) (err error) {
i.dev.DFU.Reset() var fwimg, initpkt *os.File
switch data.Type { switch data.Type {
case rpc.FirmwareUpgradeRequest_Archive: case rpc.FirmwareUpgradeRequest_Archive:
// If less than one file, return error fwimg, initpkt, err = extractDFU(data.Files[0])
if len(data.Files) < 1 {
return ErrDFUNotEnoughFiles
}
// If file is not zip archive, return error
if filepath.Ext(data.Files[0]) != ".zip" {
return ErrDFUInvalidFile
}
// Load DFU archive
err := i.dev.DFU.LoadArchive(data.Files[0])
if err != nil { if err != nil {
return err return err
} }
case rpc.FirmwareUpgradeRequest_Files: case rpc.FirmwareUpgradeRequest_Files:
// If less than two files, return error
if len(data.Files) < 2 { if len(data.Files) < 2 {
return ErrDFUNotEnoughFiles return ErrDFUNotEnoughFiles
} }
// If first file is not init packet, return error
if filepath.Ext(data.Files[0]) != ".dat" { if filepath.Ext(data.Files[0]) != ".dat" {
return ErrDFUInvalidFile return ErrDFUInvalidFile
} }
// If second file is not firmware image, return error
if filepath.Ext(data.Files[1]) != ".bin" { if filepath.Ext(data.Files[1]) != ".bin" {
return ErrDFUInvalidFile return ErrDFUInvalidFile
} }
// Load individual DFU files
err := i.dev.DFU.LoadFiles(data.Files[0], data.Files[1]) initpkt, err = os.Open(data.Files[0])
if err != nil {
return err
}
fwimg, err = os.Open(data.Files[1])
if err != nil { if err != nil {
return err return err
} }
@@ -243,38 +277,33 @@ func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_Fi
return ErrDFUInvalidUpgType return ErrDFUInvalidUpgType
} }
go func() { defer os.Remove(fwimg.Name())
for event := range i.dev.DFU.Progress() { defer os.Remove(initpkt.Name())
_ = s.Send(&rpc.DFUProgress{ defer fwimg.Close()
Sent: int64(event.Sent), defer initpkt.Close()
Recieved: int64(event.Received),
Total: event.Total,
})
}
firmwareUpdating = false
}()
// Set firmwareUpdating
firmwareUpdating = true firmwareUpdating = true
defer func() { firmwareUpdating = false }()
// Start DFU return i.dev.UpgradeFirmware(infinitime.DFUOptions{
err := i.dev.DFU.Start() InitPacket: initpkt,
if err != nil { FirmwareImage: fwimg,
firmwareUpdating = false ProgressFunc: func(sent, received, total uint32) {
return err _ = s.Send(&rpc.DFUProgress{
} Sent: int64(sent),
Recieved: int64(received),
return nil Total: int64(total),
})
},
})
} }
type FS struct { type FS struct {
dev *infinitime.Device dev *infinitime.Device
fs *blefs.FS fs *infinitime.FS
} }
func (fs *FS) RemoveAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) { func (fs *FS) RemoveAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths { for _, path := range req.Paths {
err := fs.fs.RemoveAll(path) err := fs.fs.RemoveAll(path)
if err != nil { if err != nil {
@@ -285,7 +314,6 @@ func (fs *FS) RemoveAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, e
} }
func (fs *FS) Remove(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) { func (fs *FS) Remove(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths { for _, path := range req.Paths {
err := fs.fs.Remove(path) err := fs.fs.Remove(path)
if err != nil { if err != nil {
@@ -296,12 +324,10 @@ func (fs *FS) Remove(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, erro
} }
func (fs *FS) Rename(_ context.Context, req *rpc.RenameRequest) (*rpc.Empty, error) { func (fs *FS) Rename(_ context.Context, req *rpc.RenameRequest) (*rpc.Empty, error) {
fs.updateFS()
return &rpc.Empty{}, fs.fs.Rename(req.From, req.To) return &rpc.Empty{}, fs.fs.Rename(req.From, req.To)
} }
func (fs *FS) MkdirAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) { func (fs *FS) MkdirAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths { for _, path := range req.Paths {
err := fs.fs.MkdirAll(path) err := fs.fs.MkdirAll(path)
if err != nil { if err != nil {
@@ -312,7 +338,6 @@ func (fs *FS) MkdirAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, er
} }
func (fs *FS) Mkdir(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) { func (fs *FS) Mkdir(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths { for _, path := range req.Paths {
err := fs.fs.Mkdir(path) err := fs.fs.Mkdir(path)
if err != nil { if err != nil {
@@ -323,8 +348,6 @@ func (fs *FS) Mkdir(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error
} }
func (fs *FS) ReadDir(_ context.Context, req *rpc.PathRequest) (*rpc.DirResponse, error) { func (fs *FS) ReadDir(_ context.Context, req *rpc.PathRequest) (*rpc.DirResponse, error) {
fs.updateFS()
entries, err := fs.fs.ReadDir(req.Path) entries, err := fs.fs.ReadDir(req.Path)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -346,8 +369,6 @@ func (fs *FS) ReadDir(_ context.Context, req *rpc.PathRequest) (*rpc.DirResponse
} }
func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error { func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error {
fs.updateFS()
localFile, err := os.Open(req.Source) localFile, err := os.Open(req.Source)
if err != nil { if err != nil {
return err return err
@@ -363,15 +384,12 @@ func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error
return err return err
} }
go func() { remoteFile.ProgressFunc = func(transferred, total uint32) {
// For every progress event
for sent := range remoteFile.Progress() {
_ = s.Send(&rpc.TransferProgress{ _ = s.Send(&rpc.TransferProgress{
Total: remoteFile.Size(), Total: total,
Sent: sent, Sent: transferred,
}) })
} }
}()
io.Copy(remoteFile, localFile) io.Copy(remoteFile, localFile)
localFile.Close() localFile.Close()
@@ -381,8 +399,6 @@ func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error
} }
func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) error { func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) error {
fs.updateFS()
localFile, err := os.Create(req.Destination) localFile, err := os.Create(req.Destination)
if err != nil { if err != nil {
return err return err
@@ -396,15 +412,12 @@ func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) er
defer localFile.Close() defer localFile.Close()
defer remoteFile.Close() defer remoteFile.Close()
go func() { remoteFile.ProgressFunc = func(transferred, total uint32) {
// For every progress event
for sent := range remoteFile.Progress() {
_ = s.Send(&rpc.TransferProgress{ _ = s.Send(&rpc.TransferProgress{
Total: remoteFile.Size(), Total: total,
Sent: sent, Sent: transferred,
}) })
} }
}()
_, err = io.Copy(localFile, remoteFile) _, err = io.Copy(localFile, remoteFile)
if err != nil { if err != nil {
@@ -415,42 +428,86 @@ func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) er
} }
func (fs *FS) LoadResources(req *rpc.PathRequest, s rpc.DRPCFS_LoadResourcesStream) error { func (fs *FS) LoadResources(req *rpc.PathRequest, s rpc.DRPCFS_LoadResourcesStream) error {
resFl, err := os.Open(req.Path) return infinitime.LoadResources(req.Path, fs.fs, func(evt infinitime.ResourceLoadProgress) {
if err != nil { _ = s.Send(&rpc.ResourceLoadProgress{
return err
}
progCh, err := infinitime.LoadResources(resFl, fs.fs)
if err != nil {
return err
}
for evt := range progCh {
err = s.Send(&rpc.ResourceLoadProgress{
Name: evt.Name, Name: evt.Name,
Total: evt.Total, Total: int64(evt.Total),
Sent: evt.Sent, Sent: int64(evt.Transferred),
Operation: rpc.ResourceLoadProgress_Operation(evt.Operation), Operation: rpc.ResourceLoadProgress_Operation(evt.Operation),
}) })
})
}
func extractDFU(path string) (fwimg, initpkt *os.File, err error) {
zipReader, err := zip.OpenReader(path)
if err != nil { if err != nil {
return err return nil, nil, err
}
defer zipReader.Close()
for _, file := range zipReader.File {
if fwimg != nil && initpkt != nil {
break
}
switch filepath.Ext(file.Name) {
case ".bin":
fwimg, err = os.CreateTemp(os.TempDir(), "itd_dfu_fwimg_*.bin")
if err != nil {
return nil, nil, err
}
zipFile, err := file.Open()
if err != nil {
return nil, nil, err
}
defer zipFile.Close()
_, err = io.Copy(fwimg, zipFile)
if err != nil {
return nil, nil, err
}
err = zipFile.Close()
if err != nil {
return nil, nil, err
}
_, err = fwimg.Seek(0, io.SeekStart)
if err != nil {
return nil, nil, err
}
case ".dat":
initpkt, err = os.CreateTemp(os.TempDir(), "itd_dfu_initpkt_*.dat")
if err != nil {
return nil, nil, err
}
zipFile, err := file.Open()
if err != nil {
return nil, nil, err
}
_, err = io.Copy(initpkt, zipFile)
if err != nil {
return nil, nil, err
}
err = zipFile.Close()
if err != nil {
return nil, nil, err
}
_, err = initpkt.Seek(0, io.SeekStart)
if err != nil {
return nil, nil, err
}
} }
} }
return nil if fwimg == nil || initpkt == nil {
return nil, nil, errors.New("invalid dfu archive")
} }
func (fs *FS) updateFS() { return fwimg, initpkt, nil
if fs.fs == nil || updateFS {
// Get new FS
newFS, err := fs.dev.FS()
if err != nil {
log.Warn("Error updating BLE filesystem").Err(err).Send()
} else {
// Set FS pointer to new FS
fs.fs = newFS
// Reset updateFS
updateFS = false
}
}
} }
+16
View File
@@ -0,0 +1,16 @@
package main
import (
"sync"
"go.elara.ws/logger/log"
)
type WaitGroup struct {
*sync.WaitGroup
}
func (wg WaitGroup) Done(c string) {
log.Info("Component stopped").Str("name", c).Send()
wg.WaitGroup.Done()
}
+60 -96
View File
@@ -4,16 +4,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"go.arsenm.dev/infinitime" "go.elara.ws/itd/infinitime"
"go.arsenm.dev/infinitime/weather" "go.elara.ws/logger/log"
"go.arsenm.dev/logger/log"
) )
// METResponse represents a response from // METResponse represents a response from
@@ -32,7 +30,7 @@ type METData struct {
Instant struct { Instant struct {
Details struct { Details struct {
AirPressure float32 `json:"air_pressure_at_sea_level"` AirPressure float32 `json:"air_pressure_at_sea_level"`
AirTemperature float32 `json:"air_temperature"` Temperature float32 `json:"air_temperature"`
DewPoint float32 `json:"dew_point_temperature"` DewPoint float32 `json:"dew_point_temperature"`
CloudAreaFraction float32 `json:"cloud_area_fraction"` CloudAreaFraction float32 `json:"cloud_area_fraction"`
FogAreaFraction float32 `json:"fog_area_fraction"` FogAreaFraction float32 `json:"fog_area_fraction"`
@@ -50,6 +48,12 @@ type METData struct {
PrecipitationAmount float32 `json:"precipitation_amount"` PrecipitationAmount float32 `json:"precipitation_amount"`
} }
} `json:"next_1_hours"` } `json:"next_1_hours"`
Next6Hours struct {
Details struct {
MaxTemp float32 `json:"air_temperature_max"`
MinTemp float32 `json:"air_temperature_min"`
}
} `json:"next_6_hours"`
} }
// OSMData represents lat/long data from // OSMData represents lat/long data from
@@ -61,7 +65,14 @@ type OSMData []struct {
var sendWeatherCh = make(chan struct{}, 1) var sendWeatherCh = make(chan struct{}, 1)
func initWeather(ctx context.Context, dev *infinitime.Device) error { func sleepCtx(ctx context.Context, d time.Duration) {
select {
case <-time.After(d):
case <-ctx.Done():
}
}
func initWeather(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
if !k.Bool("weather.enabled") { if !k.Bool("weather.enabled") {
return nil return nil
} }
@@ -74,14 +85,24 @@ func initWeather(ctx context.Context, dev *infinitime.Device) error {
timer := time.NewTimer(time.Hour) timer := time.NewTimer(time.Hour)
wg.Add(1)
go func() { go func() {
defer wg.Done("weather")
for { for {
select {
case _, ok := <-ctx.Done():
if !ok {
return
}
default:
}
// Attempt to get weather // Attempt to get weather
data, err := getWeather(ctx, lat, lon) data, err := getWeather(ctx, lat, lon)
if err != nil { if err != nil {
log.Warn("Error getting weather data").Err(err).Send() log.Warn("Error getting weather data").Err(err).Send()
// Wait 15 minutes before retrying // Wait 15 minutes before retrying
time.Sleep(15 * time.Minute) sleepCtx(ctx, 15*time.Minute)
continue continue
} }
@@ -89,81 +110,28 @@ func initWeather(ctx context.Context, dev *infinitime.Device) error {
current := data.Properties.Timeseries[0] current := data.Properties.Timeseries[0]
currentData := current.Data.Instant.Details currentData := current.Data.Instant.Details
// Add temperature event icon := parseSymbol(current.Data.NextHour.Summary.SymbolCode)
err = dev.AddWeatherEvent(weather.TemperatureEvent{ if icon == infinitime.WeatherIconClear {
TimelineHeader: weather.NewHeader( switch {
weather.EventTypeTemperature, case currentData.CloudAreaFraction > 50:
time.Hour, icon = infinitime.WeatherIconHeavyClouds
), case currentData.CloudAreaFraction == 50:
Temperature: int16(round(currentData.AirTemperature * 100)), icon = infinitime.WeatherIconClouds
DewPoint: int16(round(currentData.DewPoint)), case currentData.CloudAreaFraction > 0:
}) icon = infinitime.WeatherIconFewClouds
if err != nil { }
log.Error("Error adding temperature event").Err(err).Send()
} }
// Add precipitation event err = dev.SetCurrentWeather(infinitime.CurrentWeather{
err = dev.AddWeatherEvent(weather.PrecipitationEvent{ Time: time.Now(),
TimelineHeader: weather.NewHeader( CurrentTemp: currentData.Temperature,
weather.EventTypePrecipitation, MaxTemp: current.Data.Next6Hours.Details.MaxTemp,
time.Hour, MinTemp: current.Data.Next6Hours.Details.MinTemp,
), Location: k.String("weather.location"),
Type: parseSymbol(current.Data.NextHour.Summary.SymbolCode), Icon: icon,
Amount: uint8(round(current.Data.NextHour.Details.PrecipitationAmount)),
}) })
if err != nil { if err != nil {
log.Error("Error adding precipitation event").Err(err).Send() log.Error("Error setting weather").Err(err).Send()
}
// Add wind event
err = dev.AddWeatherEvent(weather.WindEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypeWind,
time.Hour,
),
SpeedMin: uint8(round(currentData.WindSpeed)),
SpeedMax: uint8(round(currentData.WindSpeed)),
DirectionMin: uint8(round(currentData.WindDirection)),
DirectionMax: uint8(round(currentData.WindDirection)),
})
if err != nil {
log.Error("Error adding wind event").Err(err).Send()
}
// Add cloud event
err = dev.AddWeatherEvent(weather.CloudsEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypeClouds,
time.Hour,
),
Amount: uint8(round(currentData.CloudAreaFraction)),
})
if err != nil {
log.Error("Error adding clouds event").Err(err).Send()
}
// Add humidity event
err = dev.AddWeatherEvent(weather.HumidityEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypeHumidity,
time.Hour,
),
Humidity: uint8(round(currentData.RelativeHumidity)),
})
if err != nil {
log.Error("Error adding humidity event").Err(err).Send()
}
// Add pressure event
err = dev.AddWeatherEvent(weather.PressureEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypePressure,
time.Hour,
),
Pressure: int16(round(currentData.AirPressure)),
})
if err != nil {
log.Error("Error adding pressure event").Err(err).Send()
} }
// Reset timer to 1 hour // Reset timer to 1 hour
@@ -174,6 +142,8 @@ func initWeather(ctx context.Context, dev *infinitime.Device) error {
select { select {
case <-timer.C: case <-timer.C:
case <-sendWeatherCh: case <-sendWeatherCh:
case <-ctx.Done():
return
} }
} }
}() }()
@@ -189,6 +159,7 @@ func getLocation(ctx context.Context, loc string) (lat, lon float64, err error)
if err != nil { if err != nil {
return return
} }
req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.elara.ws/Elara6331/itd", strings.TrimSpace(version)))
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return return
@@ -240,7 +211,7 @@ func getWeather(ctx context.Context, lat, lon float64) (*METResponse, error) {
} }
// Set identifying user agent as per NMI requirements // Set identifying user agent as per NMI requirements
req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.arsenm.dev/Arsen6331/itd", version)) req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.elara.ws/Elara6331/itd", strings.TrimSpace(version)))
// Perform request // Perform request
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
@@ -258,26 +229,19 @@ func getWeather(ctx context.Context, lat, lon float64) (*METResponse, error) {
return out, nil return out, nil
} }
// parseSymbol determines what type of precipitation a symbol code // parseSymbol determines what weather icon a symbol code codes for.
// codes for. func parseSymbol(symCode string) infinitime.WeatherIcon {
func parseSymbol(symCode string) weather.PrecipitationType {
switch { switch {
case strings.Contains(symCode, "lightrain"): case strings.Contains(symCode, "lightrain"):
return weather.PrecipitationTypeRain return infinitime.WeatherIconRain
case strings.Contains(symCode, "rain"): case strings.Contains(symCode, "rain"):
return weather.PrecipitationTypeRain return infinitime.WeatherIconCloudsWithRain
case strings.Contains(symCode, "snow"): case strings.Contains(symCode, "snow"),
return weather.PrecipitationTypeSnow strings.Contains(symCode, "sleet"):
case strings.Contains(symCode, "sleet"): return infinitime.WeatherIconSnow
return weather.PrecipitationTypeSleet case strings.Contains(symCode, "thunder"):
case strings.Contains(symCode, "snow"): return infinitime.WeatherIconThunderstorm
return weather.PrecipitationTypeSnow
default: default:
return weather.PrecipitationTypeNone return infinitime.WeatherIconClear
} }
} }
// round rounds 32-bit floats to 32-bit integers
func round(f float32) int32 {
return int32(math.Round(float64(f)))
}