303 Commits

Author SHA1 Message Date
Yannick Ulrich dc53ead339 Fixed 'touch' behaviour
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-03-11 14:08:49 +00:00
Yannick Ulrich 9bbdc3bd52 fixed typo 2023-03-11 14:05:13 +00:00
Yannick Ulrich b28c386c4e Used new error conversion routine
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-03-04 09:19:27 +00:00
Yannick Ulrich 4c59561a99 Added error to syscall function
Some of these aren't ideal..
2023-03-04 09:14:49 +00:00
Yannick Ulrich 955e1323ce Send better syscall status codes
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-03-01 19:36:44 +00:00
Yannick Ulrich 3b9690103b Moved starting of fuse
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-03-01 19:07:14 +00:00
Yannick Ulrich b5328ece92 Made logging more consistent 2023-03-01 19:06:32 +00:00
Yannick Ulrich 2396623c73 Formatted interface checks 2023-03-01 18:57:02 +00:00
Yannick Ulrich a54ca7afdf Handle more errors 2023-03-01 18:08:58 +00:00
Yannick Ulrich 673383f795 Stylistic changes 2023-03-01 18:08:18 +00:00
Yannick Ulrich cc6fc3e1dc removed godebug dep
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-03-01 16:45:42 +00:00
Yannick Ulrich c05d4fe951 4. Split converters 2023-03-01 15:15:32 +00:00
Yannick Ulrich 87c78566c1 3. Added builder 2023-03-01 15:14:06 +00:00
Yannick Ulrich 8dd8f3d012 2. added fuse.go in main 2023-03-01 15:13:49 +00:00
Yannick Ulrich bf13b96660 1. moved fuse.go 2023-03-01 15:04:17 +00:00
Yannick Ulrich 2440cb954c Merged two properties lists 2023-02-26 19:11:30 +00:00
Yannick Ulrich 08c8d7e8de Removed device wrapper 2023-02-26 19:09:45 +00:00
Yannick Ulrich 1799c072ff Added one-shot converter for strings 2023-02-26 19:08:35 +00:00
Yannick Ulrich c5ca30cd0a Added format converters 2023-02-26 19:08:10 +00:00
Yannick Ulrich c046c67dbd Turned generator to return []byte 2023-02-26 19:07:09 +00:00
Yannick Ulrich e2bd52b5a0 Implemented Read for watcher 2023-02-26 19:06:11 +00:00
Yannick Ulrich 2c899b4660 Implemented Read for watcher 2023-02-26 19:04:47 +00:00
Yannick Ulrich 083da5b3f0 Used generator instead of function in ITProperty 2023-02-26 19:03:27 +00:00
Yannick Ulrich 4333b83ca2 Added channel reader 2023-02-26 19:02:23 +00:00
Yannick Ulrich d2dbcd8713 Fixed unlink (1a5970a0) 2023-02-26 13:54:57 +00:00
Yannick Ulrich 8f57a0be8d Added fuse to config 2023-02-26 13:40:44 +00:00
Yannick Ulrich 4389609500 Run Readdir during Lookup if necessary 2023-02-26 13:15:09 +00:00
Yannick Ulrich 5fa49adee4 7. Activate fuse 2023-02-26 13:10:03 +00:00
Yannick Ulrich fe43a608d0 6. Used io.Copy to move files 2023-02-26 13:09:41 +00:00
Yannick Ulrich c142d97ee8 5. Use defer for closing file 2023-02-26 13:09:17 +00:00
Yannick Ulrich 9b9ab67217 4. Better error handling 2023-02-26 13:08:14 +00:00
Yannick Ulrich a27cc090dd 3. Interfaced FS 2023-02-26 13:06:58 +00:00
Yannick Ulrich ffe9d43cf8 2. Inherited from infinitime.Device to extend 2023-02-26 13:05:58 +00:00
Yannick Ulrich ec39e649c5 1. Removed main routine 2023-02-26 13:04:44 +00:00
Yannick Ulrich b181785006 Removed mock 2023-02-26 13:03:19 +00:00
Yannick Ulrich 4d3bc1ed40 y 1a5970a 2023-02-21 20:13:44 +00:00
Yannick Ulrich 68f6d20719 x 2023-02-21 20:11:38 +00:00
Yannick Ulrich bfb21ea6a9 Enforced single thread fuse 2023-02-21 20:06:09 +00:00
Yannick Ulrich 83726c9427 Changed the way DirEntry is mocked 2023-02-21 20:05:49 +00:00
Yannick Ulrich afaa5990c4 Ensure Close is always called 2023-02-21 19:58:02 +00:00
Yannick Ulrich 1a5970a041 Improved logging 2023-02-21 19:57:36 +00:00
Yannick Ulrich 5d71ae245c Added Fsync 2023-02-19 20:12:38 +00:00
Yannick Ulrich 9d28a2a1f8 Implementing remove 2023-02-19 20:11:30 +00:00
Yannick Ulrich 82ed906857 Implemented Rename 2023-02-19 19:58:34 +00:00
Yannick Ulrich 9d3fdeb78f Implemented Mkdir 2023-02-19 19:46:38 +00:00
Yannick Ulrich 85b2145aa5 Implemented Create 2023-02-19 19:39:26 +00:00
Yannick Ulrich 74afbc7c20 Implemented Getattr 2023-02-19 19:36:56 +00:00
Yannick Ulrich 2a8de96773 Implemented Write 2023-02-19 19:00:20 +00:00
Yannick Ulrich a8dc017a48 Implemented Read 2023-02-19 16:54:19 +00:00
Yannick Ulrich d04af07dbe Implemented ReadDir 2023-02-19 16:45:51 +00:00
Yannick Ulrich a5d70e373c Implemented mocks 2023-02-19 14:23:04 +00:00
Yannick Ulrich e4f8d0b551 Updated mock interfaces 2023-02-19 13:51:06 +00:00
Yannick Ulrich 4a18622d37 Added functions for variables 2023-02-19 13:01:18 +00:00
Yannick Ulrich 5008cdd4f4 Define reader 2023-02-19 12:23:44 +00:00
Yannick Ulrich eb745def04 Store inode in ITNode 2023-02-19 12:23:18 +00:00
Yannick Ulrich 0e0bfdc1f4 Defined ITProperties 2023-02-19 12:08:56 +00:00
Yannick Ulrich b820d6a674 Used switch instead of if 2023-02-19 12:08:45 +00:00
Yannick Ulrich 3a8888c003 Added FUSE example 2023-02-19 11:50:02 +00:00
Yannick Ulrich 7010dc6b17 Added fuse dep 2023-02-19 11:01:23 +00:00
Elara6331 4cdd47311f Switch from zerolog to go.arsenm.dev/logger in itctl
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-04 15:17:14 -08:00
Elara6331 520c23b75b Switch from zerolog to go.arsenm.dev/logger
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-04 15:06:05 -08:00
Elara6331 81840d411d Upgrade go.arsenm.dev/drpc
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-04 14:25:58 -08:00
Elara6331 0aa89e18b6 Add error handling for RPC registration functions
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 16:47:25 -08:00
Elara6331 c3a61b5893 Move multiplexing code into separate module
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 13:06:38 -08:00
Elara6331 52686fbad0 Update itgui screenshots
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:39:21 -08:00
Elara6331 547c79f874 Add doc comments
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:30:04 -08:00
Elara6331 de3ce406e7 Use multiplexed connection in NewFromConn()
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:24:36 -08:00
Elara6331 27cd275ddb Run formatter
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:18:57 -08:00
Elara6331 1ad99fafc4 Use correct paths in README
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:16:59 -08:00
Elara6331 0cf36f220d Properly close multiplexed streams
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 09:15:38 -08:00
Elara6331 1cbc2f86fa Start separate goroutine for multiplexed stream handling
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 01:02:48 -08:00
Elara6331 87fdb7a30a Add connection multiplexing, fixing itgui
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-03 00:54:00 -08:00
Elara6331 7e4720ed6a Fix dependencies
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-02 23:06:04 -08:00
Elara6331 d7bd94e164 Restructure and revise README
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-02 22:42:12 -08:00
Elara6331 f9ea55910e Remove itgui CI config
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-02 22:32:04 -08:00
Elara6331 b757af7fed Switch to autogenerated DRPC framework
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-02 22:30:17 -08:00
Hunman 5f5c67f7cc Warn when Koanf read fails (#47)
ci/woodpecker/push/woodpecker Pipeline was successful
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 248beffa2f Add tests
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-08 01:16:00 -08:00
Elara6331 73a679d10b Move mpris out of pkg directory and run gofumpt
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 17:36:25 -08:00
Elara6331 d475c6905e Fix goreleaser aur config
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:24:45 -08:00
Elara6331 0e6e3848d7 Add archlinux package to goreleaser config
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:23:03 -08:00
Elara6331 ceff536e92 Add itgui.desktop to goreleaser and fix itgui permissions 2022-11-24 16:20:11 -08:00
Elara6331 5e24e8aafa Add itgui.desktop
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:18:26 -08:00
Elara6331 f4da64a8dd Prepare for itgui cross-compilation
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:16:25 -08:00
Elara6331 76320aa813 Switch version.txt target to use go generate
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 03:23:14 +00:00
Elara6331 b64e6d27d4 Merge pull request 'Move mpris implementation from infinitime library to itd, where it really belongs' (#41) from FloralExMachina/itd:master into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/41
2022-11-22 02:24:51 +00:00
razorkitty f215e4fd90 fixed type in comment about DBus
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-11-22 02:23:03 +00:00
razorkitty 1e8c9484d2 copy mpris implementation from infinitime library to itd, where it really belongs
ci/woodpecker/pr/woodpecker Pipeline was successful
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 b6c47b7383 Remove gitm config as it's no longer needed
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-19 15:38:23 -08:00
Elara6331 e97c1fef48 Remove pactl dependencies (Arsen6331/infinitime#6)
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-19 15:29:10 -08:00
Elara6331 3f2bccc40c Merge pull request 'update itctl usage screen to current output' (#39) from mashuptwice/itd:master into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/39
2022-11-19 04:04:31 +00:00
mashuptwice d80230b9d4 update itctl usage screen to current output
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-11-19 03:55:22 +00:00
Elara6331 c81ac19dda Switch badge to self-hosted CI
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-18 07:50:21 +00:00
Elara6331 4a397d4c1e Add go generate script for calculating version number
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2022-11-17 21:27:36 -08:00
Elara6331 c5fb3e1a33 Add woodpecker config
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2022-11-18 05:02:37 +00:00
Elara6331 908bd7d5f3 Add resource loading to ITD FS tab 2022-11-15 19:20:34 -08:00
Elara6331 c97fcaeefb Mention navigation support in README 2022-11-07 12:24:55 -08:00
Elara6331 992eb2e085 Add navigation support via PureMaps 2022-11-07 12:22:14 -08:00
Elara6331 f33b3d2b56 Update infinitime library 2022-10-25 12:38:02 -07:00
Elara6331 03f3968fe1 Update infinitime library 2022-10-20 01:42:23 -07:00
Elara6331 dea92c6404 Update infinitime library 2022-10-17 12:50:51 -07:00
Elara6331 006f245c10 Add warning if current InfiniTime doesn't support BLE FS (#29) 2022-10-17 12:40:51 -07:00
Elara6331 d232340edd Update infinitime library 2022-10-17 12:24:17 -07:00
Elara6331 c6458720e9 Handle error events in itctl res load command (#29) 2022-10-17 12:23:06 -07:00
Elara6331 1e072a3540 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 f639fef992 Add resource loading as part of DFU 2022-10-16 13:17:12 -07:00
Elara6331 2d0db1dcf1 Close channel once resource uploading complete 2022-10-16 12:42:59 -07:00
Elara6331 4efa4380c4 Add -r for rm and -p for mkdir 2022-09-03 16:28:25 -07:00
Elara6331 fca64afbf3 Remove example comments from goreleaser config 2022-09-01 15:09:49 -07:00
Elara6331 cf24c5ace8 Fix file extension of Alpine example 2022-09-01 15:02:11 -07:00
Elara6331 a25b2e3e62 Add --allow-untrusted to Alpine example 2022-09-01 15:00:03 -07:00
Elara6331 2d0b64d92f Add installation instructions for major distros 2022-09-01 14:58:33 -07:00
Elara6331 5efafe9be7 Remove download binary badge and add new AUR badge 2022-09-01 14:33:23 -07:00
Elara6331 271510d528 Allow automatic release to the AUR 2022-09-01 11:50:41 -07:00
Elara6331 4d72a063b2 Update CI badge 2022-09-01 03:14:05 -07:00
Elara6331 643245f16c Add GoReleaser config 2022-09-01 01:52:46 -07:00
Elara6331 6f87980d4b Add resource loading to itctl 2022-08-30 13:01:36 -07:00
Elara6331 851f1975d6 Add LoadResources() to API 2022-08-30 12:13:22 -07:00
Elara6331 5e66fe82ac Improve itgui compilation documentation (Fixes #24) 2022-08-29 13:28:52 -07:00
Elara6331 645541e079 Update infinitime library and bluetooth library (Fixes #22) 2022-08-19 14:05:30 -07:00
Elara6331 1012be6e5b Revert #22 bandaid fix 2022-08-19 14:04:08 -07:00
Elara6331 5973290d6c Temporary bandaid fix for #22 2022-08-14 00:05:56 -07:00
Elara6331 19bacf29b2 Fix comment above goroutine code 2022-07-31 02:40:46 -07:00
Elara6331 a78650e526 Fix bug where itctl doesn't exit on SIGINT/SIGTERM 2022-07-31 02:22:33 -07:00
Elara6331 71e9caf0bc Fix bug where help command doesn't show flags/subcommands 2022-07-31 02:15:42 -07:00
Elara6331 1f5a6365bc Remove GOFLAGS from Makefile as the go tool already looks at that variable 2022-07-22 10:36:19 -07:00
Elara6331 958f2af516 Propagate context to lrpc 2022-05-12 17:14:34 -07:00
Elara6331 60f1eedc9a Create and propagate contexts wherever possible 2022-05-11 13:24:12 -07:00
Elara6331 c05147518d Add metrics screenshot 2022-05-11 12:10:50 -07:00
Elara6331 422f844943 Add metrics graphs to itgui 2022-05-10 23:37:58 -07:00
Elara6331 66618e5bf0 Add metrics collection via sqlite 2022-05-10 18:03:37 -07:00
Elara6331 0c2e57ced0 Update lrpc 2022-05-10 02:12:52 -07:00
Elara6331 6d9f6fc6e6 Update Go compiler requirement 2022-05-09 21:46:03 -07:00
Elara6331 0cbd6a48ae Allow API client to be made from connection 2022-05-07 21:23:42 -07:00
Elara6331 b614138f6b Update lrpc library 2022-05-07 15:12:29 -07:00
Elara6331 3a0491f069 Add new itgui screenshots 2022-05-05 14:05:58 -07:00
Elara6331 093a5632c7 Rewrite itgui and add new screenshots 2022-05-05 14:00:49 -07:00
Elara6331 91662e6f38 Update infinitime library 2022-05-05 12:41:51 -07:00
Elara6331 931966bf1e Update lrpc for data race fixes 2022-05-04 16:16:28 -07:00
Elara6331 ed01700e26 Update lrpc 2022-05-03 18:55:37 -07:00
Elara6331 e9269e8eb8 Update infinitime library 2022-05-02 20:21:47 -07:00
Elara6331 52b85ab361 Allow changing bluetooth adapter ID 2022-05-02 20:17:38 -07:00
Elara6331 bc45943bdc Update lrpc 2022-05-02 16:28:25 -07:00
Elara6331 6933f45683 Fix lrpc response line number in README 2022-05-01 23:06:27 -07:00
Elara6331 0f22d67395 Update lrpc 2022-05-01 21:39:58 -07:00
Elara6331 6da03181a9 Remove version.txt on clean 2022-05-01 21:22:15 -07:00
Elara6331 44a25625da Only update version if version.txt does not exist 2022-05-01 21:21:22 -07:00
Elara6331 14a38351e4 Update README to reflect recent changes 2022-05-01 21:16:47 -07:00
Elara6331 4c27f424b2 Remove debug print 2022-05-01 20:56:14 -07:00
Elara6331 86fbef2e8a Remove the no-longer useful none type alias 2022-05-01 20:51:13 -07:00
Elara6331 7b8658e072 Remove version.txt 2022-05-01 20:49:42 -07:00
Elara6331 73c46cfa66 Remove replace directive and fix firmware upgrade error 2022-05-01 20:40:30 -07:00
Elara6331 1e0f1c5b76 Fix bug where itctl could not be killed 2022-05-01 20:32:59 -07:00
Elara6331 78b5ca1de8 Add context support and update lrpc 2022-05-01 15:22:28 -07:00
Elara6331 b0c4574481 Remove now unnecessary DoneMap 2022-05-01 14:00:31 -07:00
Elara6331 01975f207c Upgrade lrpc version 2022-05-01 13:59:40 -07:00
Elara6331 428e7967c1 Use default codec 2022-05-01 11:41:16 -07:00
Elara6331 56dbf0540e Switch to lrpc and use context to handle signals 2022-05-01 11:36:28 -07:00
Elara6331 240e7a5ee4 Use rpcxlite 2022-04-30 03:25:27 -07:00
Elara6331 625805fe96 Add comments 2022-04-24 00:58:39 -07:00
Elara6331 4b6f7d408e Support bidirectional requests over gateway 2022-04-24 00:54:04 -07:00
Elara6331 9034ef7c6b Add debug logs 2022-04-23 20:20:13 -07:00
Elara6331 9939f724c4 Re-add watch commands to itctl 2022-04-23 18:46:49 -07:00
Elara6331 8dce33f7b1 Enable RPCX gateway 2022-04-23 11:29:16 -07:00
Elara6331 563009c44d Merge branch 'master' of ssh://192.168.100.62:2222/Arsen6331/itd 2022-04-22 19:22:32 -07:00
Elara6331 d4a8a9f8c9 Improve error handling 2022-04-22 18:43:13 -07:00
Elara6331 7fd9af3288 Remove old code comment 2022-04-22 17:19:23 -07:00
Elara6331 4508559bfd Update module go version to 1.17 2022-04-22 17:15:41 -07:00
Elara6331 0cdf8a4bed Switch from custom socket API to rpcx 2022-04-22 17:12:30 -07:00
Elara6331 2af6c1887f Fix typo in code (Czeck -> Czech) 2022-04-16 10:15:55 -07:00
Elara6331 3a3f95acdf Fix typo (Czeck -> Czech) 2022-04-16 10:14:18 -07:00
Elara6331 d318c584da Use new changes in infinitime library to stop removing InfiniTime devices (Fixes #10) 2022-04-16 04:28:53 -07:00
Elara6331 c8c617c10a Fix itctl panic when itd is not running (Fixes #14) 2022-04-02 15:20:31 -07:00
Elara6331 365414f951 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 9b04d06560 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 23e9195e70 Remove debug code 2022-03-15 19:25:37 -07:00
Elara6331 cd68fbd7f3 Update 'cmd/itctl/main.go' 2022-03-15 16:16:44 -07:00
Elara6331 205a041758 Remove exit error handler because it causes duplicated help text 2022-03-15 16:06:05 -07:00
Elara6331 62597f70ee Transliterate song metadata (Fixes #13) 2022-03-11 13:14:23 -08:00
Elara6331 32bb141244 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 f28c68438a Add Romanian to README.md 2022-03-11 04:17:12 -08:00
eugenr aa90e9eb26 Romanian translit 2022-03-11 04:15:10 -08:00
Elara6331 553709ce8d Make sure fs is only updated if dev.FS() succeeds (#11) 2022-03-08 08:32:31 -08:00
Elara6331 2ded0d36b1 Update infinitime library for #9 fix 2022-03-04 12:05:58 -08:00
Elara6331 a885eacc70 Rewrite itctl to use urfave/cli instead of spf13/cobra 2022-02-24 21:26:40 -08:00
Elara6331 9e63401db3 Add update weather command to itctl 2022-02-23 21:22:03 -08:00
Elara6331 2f14e70721 Add default version.txt file 2022-02-22 08:44:50 -08:00
Elara6331 614d14e399 Add version flag 2022-02-22 08:43:29 -08:00
Elara6331 c08ddfd810 Add enable switch for weather to config 2022-02-22 08:33:27 -08:00
Elara6331 4bdb82b1bc Add error logging for weather 2022-02-21 16:27:04 -08:00
Elara6331 b4d302caf6 Implement weather via MET Norway 2022-02-21 16:18:52 -08:00
Elara6331 4b2694ee0d Switch from viper to koanf 2022-02-21 11:20:02 -08:00
Elara6331 4c36144b0b Update version of infinitime library for rewritten connection code 2022-02-21 02:47:48 -08:00
Elara6331 e88dea40fb Reorganize and clean code 2021-12-17 00:31:05 -08:00
Elara6331 23b9cfe8a3 Update Infinitime library to use custom agent 2021-12-16 21:32:06 -08:00
Elara6331 518fe74e96 Propagate FS errors on read/write and close files when finished writing 2021-12-13 09:58:34 -08:00
Elara6331 27aabdceba Make paths absolute for firmware upgrades 2021-12-12 17:46:50 -08:00
Elara6331 c019d7523b Implement file transfer progress 2021-12-12 17:08:48 -08:00
Elara6331 03c3c6b22f Remove debug code 2021-12-11 22:23:01 -08:00
Elara6331 69d1027f01 Create absolute directories for ITD to read/write 2021-12-11 22:13:21 -08:00
Elara6331 873df67d1f Directly read/write files from ITD 2021-12-11 22:11:01 -08:00
Elara6331 a9ef386883 Fix comments in filesystem commands 2021-11-27 00:11:37 -08:00
Elara6331 24cfda82d7 Fix and add error messages to fs operations 2021-11-27 00:03:13 -08:00
Elara6331 655af5c446 Ensure that the FS works after a reconnect 2021-11-25 20:35:03 -08:00
Elara6331 b363a20a9d Remove replace directive 2021-11-25 19:49:07 -08:00
Elara6331 f5d326124d Add missing responses to some FS operations 2021-11-25 19:46:04 -08:00
Elara6331 cb8fb2c0bc Add newline for read file command if output is stdout 2021-11-25 19:44:43 -08:00
Elara6331 38119435f1 Get BLE FS once rather than on every connection 2021-11-25 19:41:44 -08:00
Elara6331 034a69c12f Allow multiple call notification responses 2021-11-25 12:41:36 -08:00
Elara6331 70006a3d7b Remove playerctl from depencency list 2021-11-24 16:46:57 -08:00
Elara6331 8aada58d64 Update music control implementation 2021-11-24 16:44:36 -08:00
Elara6331 5d231207cd Remove useless function call 2021-11-24 13:07:48 -08:00
Elara6331 584d9426e6 Make sure modemmanager exists for call notifications 2021-11-24 13:04:20 -08:00
Elara6331 7a772a5458 Add comments 2021-11-24 12:00:44 -08:00
Elara6331 079c733b60 Use clearer variable names 2021-11-24 11:54:16 -08:00
Elara6331 e24a8e9088 Use new helper functions 2021-11-24 11:52:52 -08:00
Elara6331 0b5d777077 Switch calls to use dbus library and add helpers for private connections 2021-11-24 11:36:36 -08:00
Elara6331 75327286ef Switch to private bus connection 2021-11-23 22:03:41 -08:00
Elara6331 099b0cd849 Add filesystem to itctl 2021-11-23 14:14:45 -08:00
Elara6331 c9c00e0072 Allow multiple paths in mkdir and remove 2021-11-23 13:35:18 -08:00
Elara6331 b2ffb2062a Fix write file in api package 2021-11-23 11:19:21 -08:00
Elara6331 2e8c825fff Add BLE FS to API package 2021-11-23 11:12:16 -08:00
Elara6331 7b870950d1 Implement BLE FS 2021-11-22 22:04:09 -08:00
Elara6331 3a877c41a4 Update infinitime library to fix compatibility with BlueZ 5.62 2021-11-22 01:18:40 -08:00
Elara6331 04fb390bee Add reminder to validate firmware to itctl and itgui 2021-11-06 19:06:17 -07:00
Elara6331 50b17d3266 Update default values to reflect new config fields 2021-11-01 11:28:55 -07:00
Elara6331 763d408405 Upgrade infinitime library version 2021-11-01 11:21:37 -07:00
Elara6331 fbb7cd9bc1 Remove config version field 2021-10-27 08:34:10 -07:00
Elara6331 f1b7f70313 Add whitelist support 2021-10-27 07:27:12 -07:00
Elara6331 552f19676b Add motion service to itgui 2021-10-25 09:45:19 -07:00
Elara6331 9d58ea0ae7 Remove debug print and add error handling (itgui) 2021-10-25 00:11:41 -07:00
Elara6331 76875db7ea Use api package in itgui 2021-10-24 13:27:14 -07:00
Elara6331 be5bdc625b Add watch commands to itctl 2021-10-24 11:02:29 -07:00
Elara6331 0d0db949af Handle unknown request type 2021-10-24 01:11:57 -07:00
Elara6331 0d164aef3d Use request type for error response type 2021-10-24 01:09:27 -07:00
Elara6331 28610d9ebb Fix API package 2021-10-24 00:49:48 -07:00
Elara6331 dff34b484d Return request type for response type 2021-10-24 00:45:50 -07:00
Elara6331 2ea9f99db6 Disable firmware updating error once progress channel closed 2021-10-23 22:03:33 -07:00
Elara6331 44dc5f8e47 Use sent bytes to check if transfer complete 2021-10-23 19:36:23 -07:00
Elara6331 4d35912466 Remove test 2021-10-23 18:42:22 -07:00
Elara6331 ef29b9bee4 Update itctl to use api 2021-10-23 18:41:03 -07:00
Elara6331 e198b769f9 Generalize socket cancellation and update API accordingly 2021-10-23 18:03:17 -07:00
Elara6331 ef4bad94b5 Reorganize itctl structure 2021-10-23 15:11:04 -07:00
Elara6331 8cf2b47733 Add cancellation to api package 2021-10-22 22:30:58 -07:00
Elara6331 f20fdcb161 Add responses to cancellation requests 2021-10-22 22:15:35 -07:00
Elara6331 eeba9b2964 Add cancellation to watchable values 2021-10-22 22:14:01 -07:00
Elara6331 d7057e3f9c Add doc comments to api package 2021-10-22 21:01:18 -07:00
Elara6331 80a5867d6b Send response types in socket responses and create api package 2021-10-22 20:47:57 -07:00
Elara6331 f001dd6079 Update readme 2021-10-22 17:12:46 -07:00
Elara6331 b87586ef15 Add MotionValues type 2021-10-22 13:42:33 -07:00
Elara6331 295892c8a8 Add motion service to itctl 2021-10-22 13:40:16 -07:00
Elara6331 1492db7566 Implement motion service 2021-10-22 13:21:14 -07:00
Elara6331 e7de7bd7bb Update infinitime library version to fix intermittent DFU issues 2021-10-21 20:27:58 -07:00
Elara6331 7b849a3fc7 Remove replace directive 2021-10-15 00:27:10 -07:00
Elara6331 21d4964207 Mention call notifications in readme 2021-10-15 00:26:14 -07:00
Elara6331 604ea57c5f Add call notifications for ModemManager 2021-10-15 00:25:34 -07:00
Elara6331 b15cbb6349 Show update file names when selected in itgui 2021-10-07 13:38:13 -07:00
Elara6331 843e369bab Remove replace directive 2021-10-06 17:47:07 -07:00
Elara6331 eec7a3db48 Update infinitime library 2021-10-06 17:15:42 -07:00
Elara6331 c23201e18c Add and fix comments, fix transliteration maps so only first character is capitalized 2021-10-06 13:26:16 -07:00
Elara6331 4bc6eb9d41 Fix chinese transliteration when chinese characters are not followed by non-chinese characters 2021-10-06 13:15:49 -07:00
Elara6331 2a59e74a2c Only do init once for Armenian transliteration 2021-10-06 13:08:25 -07:00
Elara6331 df743cca96 Add init functions to transliterators 2021-10-06 09:41:33 -07:00
Elara6331 01bf493c77 Fix capital letters for Armenian transliteration 2021-10-05 09:09:19 -07:00
Elara6331 b6e9ad6160 Add Chinese transliteration via Pinyin conversion library 2021-10-04 22:26:16 -07:00
Elara6331 c56c0ae198 Update variable names and comments for interface-based transliteration 2021-10-04 20:23:54 -07:00
Elara6331 6b94030b83 Fix Korean transliteration 2021-10-04 20:06:08 -07:00
Elara6331 2bbd722ecd Add korean transliteration 2021-10-04 19:07:54 -07:00
Elara6331 73f16fcfef Use interface to allow for more complex transliteration implementations 2021-10-04 17:45:26 -07:00
Elara6331 9df6531023 Fix German transliteration for Ü and add attribution 2021-10-04 13:17:48 -07:00
Elara6331 1db2ca3395 Add transliteration 2021-10-04 01:05:01 -07:00
Elara6331 419b2f5a79 Break transfer loops after refreshing progress bar 2021-08-27 09:01:46 -07:00
Elara6331 44607ba9e2 Add fatal error dialog 2021-08-27 08:47:24 -07:00
Elara6331 f4d2f4e6eb Mention GUI in README 2021-08-26 09:01:03 -07:00
Elara6331 0721b7f9d4 Add comments to gui 2021-08-26 08:47:17 -07:00
Elara6331 b7bd385c43 Add GUI frontend 2021-08-25 21:18:24 -07:00
Elara6331 cbcefb149e Fix indentation in config 2021-08-24 20:35:25 -07:00
Elara6331 cb8d207249 Switch to iota for request types and move to types package 2021-08-24 20:32:17 -07:00
Elara6331 7786ea1d58 Fix debug config paths 2021-08-24 08:55:22 -07:00
Elara6331 6e16aa7a7a Add config defaults and run go fmt 2021-08-24 08:54:08 -07:00
Elara6331 91f7132d5e Create new config format 2021-08-24 08:33:41 -07:00
Elara6331 b186f77bea Remove replace directive 2021-08-22 15:07:45 -07:00
Elara6331 adb297c6dd Fix find and replace error 2021-08-22 13:53:32 -07:00
Elara6331 b4992cb393 Update infinitime library to fix connection bug 2021-08-22 13:13:37 -07:00
Elara6331 a5490b8364 Use new pair timeout option 2021-08-21 20:35:21 -07:00
Elara6331 44d1f5552b Mention interactive mode in readme 2021-08-21 19:07:59 -07:00
Elara6331 5e34f656b3 Disable completion command 2021-08-21 19:03:18 -07:00
Elara6331 560d19860e Fix binary download link 2021-08-21 18:52:11 -07:00
Elara6331 ea1a7fa9f4 Add badges 2021-08-21 18:48:43 -07:00
Elara6331 81fe634ed8 Add uninstall rule to makefile 2021-08-21 17:17:25 -07:00
Elara6331 281e1dcbac Add CI status to readme 2021-08-21 16:36:10 -07:00
Elara6331 4847eee540 Add GOFLAGS environment variable to makefile 2021-08-21 16:03:54 -07:00
Elara6331 19caa3ee83 Add gitm for mirroring 2021-08-21 15:59:19 -07:00
Elara6331 e523a024ec Specify minimum go version in readme 2021-08-21 15:35:36 -07:00
Elara6331 4a3dff646c Add starting to readme 2021-08-21 15:14:37 -07:00
Elara6331 986d2064a7 Change recoverable errors to warn log level to stop shell from exiting 2021-08-21 14:15:55 -07:00
Elara6331 95cf5bfe6b Add interactive mode to itctl 2021-08-21 12:30:16 -07:00
Elara6331 0d70dd9b11 Prioritize config in home directory over /etc 2021-08-21 10:26:12 -07:00
Elara6331 3cfcdb7a01 Mention getting info from watch in readme 2021-08-21 09:40:29 -07:00
Elara6331 bdf5a099d4 Mention automatic config updates in readme 2021-08-21 09:37:20 -07:00
Elara6331 4a195f8311 Watch config for changes and apply automatically 2021-08-21 03:07:48 -07:00
Elara6331 60d2053894 Remove replace directive and update infinitime library 2021-08-21 01:37:16 -07:00
Elara6331 407a6cb3d7 Initial Commit 2021-08-21 01:19:49 -07:00
56 changed files with 814 additions and 3260 deletions
-1
View File
@@ -1 +0,0 @@
liberapay: Elara6331
+7 -9
View File
@@ -14,7 +14,6 @@ builds:
- amd64
- arm
- arm64
- riscv64
goarm:
- 7
- id: itctl
@@ -44,7 +43,6 @@ archives:
- README.md
- itd.toml
- itd.service
allow_different_binary_count: true
nfpms:
- id: itd
file_name_template: >-
@@ -55,8 +53,8 @@ nfpms:
{{- else }}{{.Arch}}
{{- end }}
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
homepage: 'https://gitea.elara.ws/Elara6331/itd'
maintainer: 'Elara Ivy <elara@elara.ws>'
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd'
maintainer: 'Arsen Musyaelyan <arsen@arsenm.dev>'
license: GPLv3
formats:
- apk
@@ -76,10 +74,10 @@ nfpms:
mode: 0755
aurs:
- name: itd-bin
homepage: 'https://gitea.elara.ws/Elara6331/itd'
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd'
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
maintainers:
- 'Elara Ivy <elara@elara.ws>'
- 'Arsen Musyaelyan <arsen@arsenm.dev>'
license: GPLv3
private_key: '{{ .Env.AUR_KEY }}'
git_url: 'ssh://aur@aur.archlinux.org/itd-bin.git'
@@ -107,11 +105,11 @@ aurs:
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/itd/LICENSE"
release:
gitea:
owner: Elara6331
owner: Arsen6331
name: itd
gitea_urls:
api: 'https://gitea.elara.ws/api/v1/'
download: 'https://gitea.elara.ws'
api: 'https://gitea.arsenm.dev/api/v1/'
download: 'https://gitea.arsenm.dev'
skip_tls_verify: false
checksum:
name_template: 'checksums.txt'
+19 -15
View File
@@ -1,17 +1,11 @@
# ITD
## InfiniTime Daemon
`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).
`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).
[![status-badge](https://ci.elara.ws/api/badges/Elara6331/itd/status.svg)](https://ci.elara.ws/Elara6331/itd)
[![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/itd/status.svg)](https://ci.arsenm.dev/Arsen6331/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-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)
---
@@ -28,7 +22,6 @@ This repository is part of the Software Heritage Archive:
- Weather
- BLE Filesystem
- Navigation (PureMaps)
- FUSE Filesystem
---
@@ -42,19 +35,19 @@ Use the `itd-bin` or `itd-git` AUR packages.
#### Debian/Ubuntu
- 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.
- 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.
- 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`
#### Fedora
- 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.
- 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.
- 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`
#### Alpine (and postmarketOS)
- 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.
- 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.
- 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`
@@ -97,11 +90,11 @@ In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [Fyn
#### Easy Installation
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`.
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`.
Instructions:
1. Install LURE. This can be done with the following command: `curl https://www.elara.ws/lure.sh | bash`.
1. Install LURE. This can be done with the following command: `curl https://www.arsenm.dev/lure.sh | bash`.
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.
4. Once the process is complete, you should be able to open and use `itgui` like any other app.
@@ -144,6 +137,17 @@ 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
This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directly control the daemon and, by extension, the connected watch.
@@ -193,4 +197,4 @@ Most of the time, the daemon does not need to be restarted for config changes to
Location data from OpenStreetMap Nominatim, &copy; [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors
Weather data from the [Norwegian Meteorological Institute](https://www.met.no/en)
Weather data from the [Norwegian Meteorological Institute](https://www.met.no/en)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"io"
"net"
"go.elara.ws/drpc/muxconn"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/drpc/muxconn"
"go.arsenm.dev/itd/internal/rpc"
"storj.io/drpc"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"context"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
type DFUProgress struct {
+2 -5
View File
@@ -5,7 +5,7 @@ import (
"errors"
"io"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
type FSClient struct {
@@ -42,10 +42,7 @@ func (c *FSClient) Mkdir(ctx context.Context, paths ...string) error {
func (c *FSClient) ReadDir(ctx context.Context, dir string) ([]FileInfo, error) {
res, err := c.client.ReadDir(ctx, &rpc.PathRequest{Path: dir})
if err != nil {
return nil, err
}
return convertEntries(res.Entries), nil
return convertEntries(res.Entries), err
}
func convertEntries(e []*rpc.FileInfo) []FileInfo {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"context"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
func (c *Client) HeartRate(ctx context.Context) (uint8, error) {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"context"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
func (c *Client) Notify(ctx context.Context, title, body string) error {
+5 -5
View File
@@ -3,15 +3,15 @@ package api
import (
"context"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/rpc"
)
type ResourceOperation infinitime.ResourceOperation
type ResourceOperation uint8
const (
ResourceRemove = infinitime.ResourceRemove
ResourceUpload = infinitime.ResourceUpload
ResourceOperationRemoveObsolete = infinitime.ResourceOperationRemoveObsolete
ResourceOperationUpload = infinitime.ResourceOperationUpload
)
type ResourceLoadProgress struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"time"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
func (c *Client) SetTime(ctx context.Context, t time.Time) error {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"context"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
func (c *Client) WeatherUpdate(ctx context.Context) error {
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"context"
"go.elara.ws/itd/internal/rpc"
"go.arsenm.dev/itd/internal/rpc"
)
func (c *Client) WatchHeartRate(ctx context.Context) (<-chan uint8, error) {
+28 -57
View File
@@ -2,14 +2,15 @@ package main
import (
"context"
"sync"
"github.com/godbus/dbus/v5"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/utils"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
"go.arsenm.dev/logger/log"
)
func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
// Connect to system bus. This connection is for method calls.
conn, err := utils.NewSystemBusConn(ctx)
if err != nil {
@@ -49,40 +50,34 @@ func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) e
// Notify channel upon received message
monitorConn.Eavesdrop(callCh)
var respHandlerOnce sync.Once
var callObj dbus.BusObject
wg.Add(1)
go func() {
defer wg.Done("callNotifs")
for {
select {
case event := <-callCh:
// Get path to call object
callPath := event.Body[0].(dbus.ObjectPath)
// Get call object
callObj = conn.Object("org.freedesktop.ModemManager1", callPath)
// For every message received
for event := range callCh {
// Get path to call object
callPath := event.Body[0].(dbus.ObjectPath)
// Get call object
callObj = conn.Object("org.freedesktop.ModemManager1", callPath)
// Get phone number from call object using method call connection
phoneNum, err := getPhoneNum(conn, callObj)
if err != nil {
log.Error("Error getting phone number").Err(err).Send()
continue
}
// Get phone number from call object using method call connection
phoneNum, err := getPhoneNum(conn, callObj)
if err != nil {
log.Error("Error getting phone number").Err(err).Send()
continue
}
// Get direction of call object using method call connection
direction, err := getDirection(conn, callObj)
if err != nil {
log.Error("Error getting call direction").Err(err).Send()
continue
}
// Send call notification to InfiniTime
resCh, err := dev.NotifyCall(phoneNum)
if err != nil {
continue
}
if direction != MMCallDirectionIncoming {
continue
}
// Send call notification to InfiniTime
err = dev.NotifyCall(phoneNum, func(cs infinitime.CallStatus) {
switch cs {
go respHandlerOnce.Do(func() {
// Wait for PineTime response
for res := range resCh {
switch res {
case infinitime.CallStatusAccepted:
// Attempt to accept call
err = acceptCall(ctx, conn, callObj)
@@ -99,13 +94,8 @@ func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) e
// Warn about unimplemented muting
log.Warn("Muting calls is not implemented").Send()
}
})
if err != nil {
continue
}
case <-ctx.Done():
return
}
})
}
}()
@@ -135,25 +125,6 @@ func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
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
func acceptCall(ctx context.Context, conn *dbus.Conn, callObj dbus.BusObject) error {
// Call Accept() method on DBus object
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/cheggaaa/pb/v3"
"github.com/urfave/cli/v2"
"go.elara.ws/itd/api"
"go.elara.ws/logger/log"
"go.arsenm.dev/itd/api"
"go.arsenm.dev/logger/log"
)
func fwUpgrade(c *cli.Context) error {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"time"
"github.com/urfave/cli/v2"
"go.elara.ws/itd/api"
"go.elara.ws/logger"
"go.elara.ws/logger/log"
"go.arsenm.dev/itd/api"
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
)
var client *api.Client
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/cheggaaa/pb/v3"
"github.com/urfave/cli/v2"
"go.elara.ws/itd/infinitime"
"go.arsenm.dev/infinitime"
)
func resourcesLoad(c *cli.Context) error {
@@ -39,7 +39,7 @@ func resLoad(ctx context.Context, args []string) error {
return evt.Err
}
if evt.Operation == infinitime.ResourceRemove {
if evt.Operation == infinitime.ResourceOperationRemoveObsolete {
bar.SetTemplateString(rmTmpl)
bar.Set("filename", evt.Name)
} else {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
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/theme"
"fyne.io/fyne/v2/widget"
"go.elara.ws/itd/api"
"go.elara.ws/itd/infinitime"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/api"
)
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 {
switch evt.Operation {
case infinitime.ResourceRemove:
case infinitime.ResourceOperationRemoveObsolete:
progressDlg.SetText("Removing " + evt.Name)
case infinitime.ResourceUpload:
case infinitime.ResourceOperationUpload:
progressDlg.SetText("Uploading " + evt.Name)
progressDlg.SetTotal(float64(evt.Total))
progressDlg.SetValue(float64(evt.Sent))
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/x/fyne/widget/charts"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
_ "modernc.org/sqlite"
)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
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/container"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
func main() {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
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/layout"
"fyne.io/fyne/v2/widget"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
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/layout"
"fyne.io/fyne/v2/widget"
"go.elara.ws/itd/api"
"go.arsenm.dev/itd/api"
)
func timeTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
+3 -3
View File
@@ -9,8 +9,8 @@ import (
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"go.elara.ws/logger"
"go.elara.ws/logger/log"
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
)
var cfgDir string
@@ -107,7 +107,7 @@ func setCfgDefaults() {
"music.vol.interval": 5,
"fuse.enabled": false,
"fuse.enabled": false,
"fuse.mountpoint": "/tmp/itd/mnt",
}, "."), nil)
}
+10 -24
View File
@@ -1,26 +1,17 @@
package main
import (
"context"
"go.arsenm.dev/itd/internal/fusefs"
"os"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/fusefs"
"go.elara.ws/logger/log"
"go.arsenm.dev/logger/log"
"context"
"go.arsenm.dev/infinitime"
)
func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func startFUSE(ctx context.Context, dev *infinitime.Device) error {
// This is where we'll mount the FS
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"))
os.Mkdir(k.String("fuse.mountpoint"), 0755)
root, err := fusefs.BuildRootNode(dev)
if err != nil {
log.Error("Building root node failed").
@@ -32,7 +23,7 @@ func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error
server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{
MountOptions: fuse.MountOptions{
// Set to true to see how the file system works.
Debug: false,
Debug: false,
SingleThreaded: true,
},
})
@@ -41,7 +32,7 @@ func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error
Str("target", k.String("fuse.mountpoint")).
Err(err).
Send()
return err
return err
}
log.Info("Mounted on target").
@@ -55,12 +46,7 @@ func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error
return err
}
wg.Add(1)
go func() {
defer wg.Done("fuse")
<-ctx.Done()
server.Unmount()
}()
// Wait until unmount before exiting
go server.Serve()
return nil
}
+11 -12
View File
@@ -1,29 +1,26 @@
module go.elara.ws/itd
module go.arsenm.dev/itd
go 1.18
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 (
fyne.io/fyne/v2 v2.3.0
fyne.io/x/fyne v0.0.0-20220107050838-c4a1de51d4ce
github.com/cheggaaa/pb/v3 v3.1.0
github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d
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/mattn/go-isatty v0.0.17
github.com/mozillazg/go-pinyin v0.19.0
github.com/urfave/cli/v2 v2.23.7
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df
golang.org/x/text v0.5.0
google.golang.org/protobuf v1.28.1
modernc.org/sqlite v1.20.1
storj.io/drpc v0.0.32
tinygo.org/x/bluetooth v0.9.0
)
require (
@@ -34,19 +31,21 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.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/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/glfw-js v0.0.0-20220517201726-bebc2019cd33 // 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/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/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // 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/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
@@ -55,18 +54,18 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // 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/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/tinygo-org/cbgo v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/goldmark v1.5.3 // indirect
@@ -75,7 +74,7 @@ require (
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/tools v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect
+25 -18
View File
@@ -107,8 +107,6 @@ 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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -121,6 +119,7 @@ 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.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
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/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=
@@ -130,6 +129,8 @@ 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
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-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU=
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
@@ -160,14 +161,15 @@ 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.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.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
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-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-20221219135543-5d0d724ee181 h1:J6XG/Xx7uCCpskM71R6YAgPHd/E8FzhyPhL6Ll94uMY=
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.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -238,6 +240,7 @@ 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-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -338,8 +341,6 @@ 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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/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=
@@ -395,6 +396,8 @@ 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/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
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-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -406,6 +409,7 @@ 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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/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.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@@ -450,8 +454,6 @@ 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 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/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/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=
@@ -459,11 +461,10 @@ 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/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.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.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -498,16 +499,17 @@ 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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
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.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/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
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/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -525,10 +527,12 @@ 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/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d h1:ANb8YPtcxPipwKgmnW688e5PGpNaLh+22nO2LBpIPOU=
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d/go.mod h1:NDprjiVqKXQKVGzX7jp2g/jctsUbvOxz1nN15QOBEGk=
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae h1:d+gJUhEWSrOjrrfgeydYWEr8TTnx0DLvcVhghaOsFeE=
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98 h1:Tt/05IEOeIEfovj8YLxDaZz+SP8eR1uPLtcgD5Pf4EI=
go.arsenm.dev/drpc v0.0.0-20230104221210-aa7525743d98/go.mod h1:K5cFls42m5q1RIphTVojRdXLaoCknq/kBqQt8Ow3XuA=
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469 h1:LsJHg+8rQSYnTE1sSCjBCACxUUVMZIOQani8J6wF2/E=
go.arsenm.dev/infinitime v0.0.0-20230104230015-512d48bc2469/go.mod h1:scUyDmLmCHn6CanGbau8yjTjzyhUbLJcsjmDCCKMIII=
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.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@@ -720,6 +724,7 @@ 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-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-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-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -741,6 +746,7 @@ 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-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-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-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -748,8 +754,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-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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -812,6 +818,7 @@ 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-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-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-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-142
View File
@@ -1,142 +0,0 @@
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
@@ -1,222 +0,0 @@
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
@@ -1,617 +0,0 @@
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
@@ -1,142 +0,0 @@
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
@@ -1,173 +0,0 @@
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
@@ -1,101 +0,0 @@
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
@@ -1,68 +0,0 @@
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
@@ -1,137 +0,0 @@
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
@@ -1,42 +0,0 @@
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
@@ -1,135 +0,0 @@
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
@@ -1,58 +0,0 @@
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
@@ -1,108 +0,0 @@
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
@@ -1,124 +0,0 @@
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
@@ -1,62 +0,0 @@
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
@@ -1,212 +0,0 @@
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
@@ -0,0 +1,61 @@
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
}
+112 -141
View File
@@ -1,114 +1,93 @@
package fusefs
import (
"bytes"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/blefs"
"go.arsenm.dev/logger/log"
"context"
"io"
"strconv"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"go.elara.ws/itd/infinitime"
"go.elara.ws/logger/log"
"io"
"bytes"
)
type ITProperty struct {
name string
Ino uint64
gen func() ([]byte, error)
Ino uint64
gen func(context.Context) (<-chan []byte, error)
}
type DirEntry struct {
isDir bool
modtime uint64
size uint32
path string
isDir bool
modtime uint64
size uint32
path string
}
type ITNode struct {
fs.Inode
kind nodeKind
Ino uint64
kind int
Ino uint64
lst []DirEntry
lst []DirEntry
self DirEntry
path string
}
type nodeKind uint8
const (
nodeKindRoot = iota
nodeKindInfo
nodeKindFS
nodeKindReadOnly
)
var (
myfs *infinitime.FS = nil
inodemap map[string]uint64 = nil
)
var myfs *blefs.FS = nil
var inodemap map[string]uint64 = nil
func BuildRootNode(dev *infinitime.Device) (*ITNode, error) {
var err error
inodemap = make(map[string]uint64)
myfs = dev.FS()
myfs, err = dev.FS()
if err != nil {
log.Error("FUSE Failed to get filesystem").Err(err).Send()
return nil, err
}
return &ITNode{kind: nodeKindRoot}, nil
return &ITNode{kind: 0}, nil
}
var properties = make([]ITProperty, 6)
func BuildProperties(dev *infinitime.Device) {
properties[0] = ITProperty{
"heartrate", 2,
func() ([]byte, error) {
ans, err := dev.HeartRate()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
},
}
properties[1] = ITProperty{
"battery", 3,
func() ([]byte, error) {
ans, err := dev.BatteryLevel()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
},
}
properties[2] = ITProperty{
"motion", 4,
func() ([]byte, error) {
ans, err := dev.Motion()
return []byte(strconv.Itoa(int(ans.X)) + " " + strconv.Itoa(int(ans.Y)) + " " + strconv.Itoa(int(ans.Z)) + "\n"), err
},
}
properties[3] = ITProperty{
"stepcount", 6,
func() ([]byte, error) {
ans, err := dev.StepCount()
return []byte(strconv.Itoa(int(ans)) + "\n"), err
},
}
properties[4] = ITProperty{
"version", 7,
func() ([]byte, error) {
properties[0] = ITProperty{"heartrate", 2,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchHeartRate(ctx)
return converterU8(ctx, ans), err
}}
properties[1] = ITProperty{"battery", 3,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchBatteryLevel(ctx)
return converterU8(ctx, ans), err
}}
properties[2] = ITProperty{"motion", 4,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchMotion(ctx)
return converterMotionValues(ctx, ans), err
}}
properties[3] = ITProperty{"stepcount", 5,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.WatchStepCount(ctx)
return converterU32(ctx, ans), err
}}
properties[4] = ITProperty{"version", 6,
func(ctx context.Context) (<-chan []byte, error) {
ans, err := dev.Version()
return []byte(ans + "\n"), err
},
}
properties[5] = ITProperty{
"address", 8,
func() ([]byte, error) {
return converter1String(ctx, ans), err
}}
properties[5] = ITProperty{"address", 7,
func(ctx context.Context) (<-chan []byte, error) {
ans := dev.Address()
return []byte(ans + "\n"), nil
},
}
return converter1String(ctx, ans), nil
}}
}
var _ fs.NodeReaddirer = (*ITNode)(nil)
// Readdir is part of the NodeReaddirer interface
@@ -118,7 +97,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
// root folder
r := make([]fuse.DirEntry, 2)
r[0] = fuse.DirEntry{
Name: "info",
Name: "device",
Ino: 0,
Mode: fuse.S_IFDIR,
}
@@ -130,7 +109,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
return fs.NewListDirStream(r), 0
case 1:
// info folder
// device folder
r := make([]fuse.DirEntry, 6)
for ind, value := range properties {
r[ind] = fuse.DirEntry{
@@ -143,7 +122,7 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
return fs.NewListDirStream(r), 0
case 2:
// on info
// on device
files, err := myfs.ReadDir(n.path)
if err != nil {
log.Error("FUSE ReadDir failed").Str("path", n.path).Err(err).Send()
@@ -162,10 +141,10 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
name := info.Name()
file := DirEntry{
path: n.path + "/" + name,
size: uint32(info.Size()),
path: n.path + "/" + name,
size: uint32(info.Size()),
modtime: uint64(info.ModTime().Unix()),
isDir: info.IsDir(),
isDir: info.IsDir(),
}
n.lst[ind] = file
@@ -179,13 +158,13 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
r[ind] = fuse.DirEntry{
Name: name,
Mode: fuse.S_IFDIR,
Ino: ino + 10,
Ino : ino + 10,
}
} else {
r[ind] = fuse.DirEntry{
Name: name,
Mode: fuse.S_IFREG,
Ino: ino + 10,
Ino : ino + 10,
}
}
}
@@ -196,37 +175,36 @@ func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
}
var _ fs.NodeLookuper = (*ITNode)(nil)
func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
switch n.kind {
case 0:
// root folder
if name == "info" {
if name == "device" {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: uint64(0),
Ino: uint64(0),
}
operations := &ITNode{kind: nodeKindInfo, Ino: 0}
operations := &ITNode{kind: 1, Ino: 0}
child := n.NewInode(ctx, operations, stable)
return child, 0
} else if name == "fs" {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: uint64(1),
Ino: uint64(1),
}
operations := &ITNode{kind: nodeKindFS, Ino: 1, path: ""}
operations := &ITNode{kind: 2, Ino: 1, path : ""}
child := n.NewInode(ctx, operations, stable)
return child, 0
}
case 1:
// info folder
// device folder
for _, value := range properties {
if value.name == name {
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: uint64(value.Ino),
Ino: uint64(value.Ino),
}
operations := &ITNode{kind: nodeKindReadOnly, Ino: value.Ino}
operations := &ITNode{kind: 3, Ino: value.Ino}
child := n.NewInode(ctx, operations, stable)
return child, 0
}
@@ -239,7 +217,7 @@ func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*
}
for _, file := range n.lst {
if file.path != n.path+"/"+name {
if file.path != n.path + "/" + name {
continue
}
log.Debug("FUSE Lookup successful").Str("path", file.path).Send()
@@ -247,25 +225,26 @@ func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*
if file.isDir {
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: inodemap[file.path],
Ino: inodemap[file.path],
}
operations := &ITNode{kind: nodeKindFS, path: file.path}
operations := &ITNode{kind: 2, path: file.path}
child := n.NewInode(ctx, operations, stable)
return child, 0
} else {
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: inodemap[file.path],
Ino: inodemap[file.path],
}
operations := &ITNode{
kind: nodeKindFS, path: file.path,
kind: 2, path: file.path,
self: file,
}
child := n.NewInode(ctx, operations, stable)
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()
}
return nil, syscall.ENOENT
}
@@ -275,7 +254,6 @@ type bytesFileReadHandle struct {
}
var _ fs.FileReader = (*bytesFileReadHandle)(nil)
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()
end := off + int64(len(dest))
@@ -286,35 +264,30 @@ func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64)
}
type sensorFileReadHandle struct {
content []byte
ch <-chan []byte
cancel context.CancelFunc
}
var _ fs.FileReader = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
end := off + int64(len(dest))
if end > int64(len(fh.content)) {
end = int64(len(fh.content))
}
return fuse.ReadResultData(fh.content[off:end]), 0
content := <-fh.ch
return fuse.ReadResultData(content), 0
}
var _ fs.FileFlusher = (*sensorFileReadHandle)(nil)
func (fh *sensorFileReadHandle) Flush(ctx context.Context) (errno syscall.Errno) {
fh.cancel()
return 0
}
type bytesFileWriteHandle struct {
content []byte
path string
path string
}
var _ fs.FileWriter = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
log.Debug("FUSE Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send()
log.Info("Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send()
if off != int64(len(fh.content)) {
log.Error("FUSE Write file size changed unexpectedly").Int("expect", int(off)).Int("received", len(fh.content)).Send()
return 0, syscall.ENXIO
@@ -324,8 +297,8 @@ func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int6
}
var _ fs.FileFlusher = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno) {
log.Debug("FUSE Attempting flush").Str("path", fh.path).Send()
fp, err := myfs.Create(fh.path, uint32(len(fh.content)))
if err != nil {
@@ -342,10 +315,13 @@ func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno)
}
return 0
}
fp.ProgressFunc = func(transferred, total uint32) {
log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
}
go func() {
// For every progress event
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)
nread, err := io.Copy(fp, r)
@@ -368,15 +344,12 @@ func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno)
return 0
}
var _ fs.FileFsyncer = (*bytesFileWriteHandle)(nil)
func (fh *bytesFileWriteHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
return fh.Flush(ctx)
}
var _ fs.NodeGetattrer = (*ITNode)(nil)
func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
log.Debug("FUSE getattr").Str("path", bn.path).Send()
out.Ino = bn.Ino
@@ -388,7 +361,6 @@ func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOu
}
var _ fs.NodeSetattrer = (*ITNode)(nil)
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()
out.Size = 0
@@ -397,7 +369,6 @@ func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAtt
}
var _ fs.NodeOpener = (*ITNode)(nil)
func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
switch f.kind {
case 2:
@@ -407,11 +378,11 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
return nil, 0, syscall.EROFS
}
if openFlags&syscall.O_WRONLY != 0 {
if openFlags & syscall.O_WRONLY != 0 {
log.Debug("FUSE Opening for write").Str("path", f.path).Send()
fh = &bytesFileWriteHandle{
path: f.path,
content: make([]byte, 0),
path : f.path,
content : make([]byte, 0),
}
return fh, fuse.FOPEN_DIRECT_IO, 0
} else {
@@ -426,9 +397,12 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
b := &bytes.Buffer{}
fp.ProgressFunc = func(transferred, total uint32) {
log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
}
go func() {
// For every progress event
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)
if err != nil {
@@ -453,13 +427,15 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
for _, value := range properties {
if value.Ino == f.Ino {
ans, err := value.gen()
sub_ctx, cancel := context.WithCancel(ctx)
ans, err := value.gen(sub_ctx)
if err != nil {
return nil, 0, syscallErr(err)
}
fh = &sensorFileReadHandle{
content: ans,
ch: ans,
cancel : cancel,
}
return fh, fuse.FOPEN_DIRECT_IO, 0
}
@@ -469,7 +445,6 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
}
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) {
if f.kind != 2 {
return nil, nil, 0, syscall.EROFS
@@ -481,17 +456,17 @@ func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uin
stable := fs.StableAttr{
Mode: fuse.S_IFREG,
Ino: ino,
Ino: ino,
}
operations := &ITNode{
kind: nodeKindFS, Ino: ino,
path: path,
kind: 2, Ino: ino,
path : path,
}
node = f.NewInode(ctx, operations, stable)
fh = &bytesFileWriteHandle{
path: path,
content: make([]byte, 0),
path : path,
content : make([]byte, 0),
}
log.Debug("FUSE Creating file").Str("path", path).Send()
@@ -501,7 +476,6 @@ func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uin
}
var _ fs.NodeMkdirer = (*ITNode)(nil)
func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
if f.kind != 2 {
return nil, syscall.EROFS
@@ -522,11 +496,11 @@ func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.
stable := fs.StableAttr{
Mode: fuse.S_IFDIR,
Ino: ino,
Ino: ino,
}
operations := &ITNode{
kind: nodeKindFS, Ino: ino,
path: path,
kind: 2, Ino: ino,
path : path,
}
node := f.NewInode(ctx, operations, stable)
@@ -538,7 +512,6 @@ func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.
}
var _ fs.NodeRenamer = (*ITNode)(nil)
func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
if f.kind != 2 {
return syscall.EROFS
@@ -570,17 +543,16 @@ func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbe
}
var _ fs.NodeUnlinker = (*ITNode)(nil)
func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
if f.kind != 2 {
return syscall.EROFS
}
delete(inodemap, f.path+"/"+name)
delete(inodemap, f.path + "/" + name)
err := myfs.Remove(f.path + "/" + name)
if err != nil {
log.Error("FUSE Unlink failed").
Str("file", f.path+"/"+name).
Str("file", f.path + "/" + name).
Err(err).
Send()
@@ -588,13 +560,12 @@ func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
}
log.Debug("FUSE Unlink success").
Str("file", f.path+"/"+name).
Str("file", f.path + "/" + name).
Send()
return 0
}
var _ fs.NodeRmdirer = (*ITNode)(nil)
func (f *ITNode) Rmdir(ctx context.Context, name string) syscall.Errno {
return f.Unlink(ctx, name)
}
+54 -59
View File
@@ -1,9 +1,7 @@
package fusefs
import (
"go.arsenm.dev/infinitime/blefs"
"syscall"
"go.elara.ws/itd/internal/fsproto"
)
func syscallErr(err error) syscall.Errno {
@@ -11,62 +9,59 @@ func syscallErr(err error) syscall.Errno {
return 0
}
switch err := err.(type) {
case fsproto.Error:
switch err.Code {
case 0x02: // filesystem error
return syscall.EIO
case 0x05: // read-only filesystem
return syscall.EROFS
case 0x03: // no such file
return syscall.ENOENT
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
case -20: // entry is not a directory
return syscall.ENOTDIR
case -39: // directory is not empty
return syscall.ENOTEMPTY
case -9: // bad file number
return syscall.EBADF
case -27: // file is too large
return syscall.EFBIG
case -22: // invalid parameter
return syscall.EINVAL
case -28: // no space left on device
return syscall.ENOSPC
case -12: // no more memory available
return syscall.ENOMEM
case -61: // no attr available
return syscall.ENODATA
case -36: // file name is too long
return syscall.ENAMETOOLONG
}
default:
switch err {
case fsproto.ErrFileNotExists: // file does not exist
return syscall.ENOENT
case fsproto.ErrFileReadOnly: // file is read only
return syscall.EACCES
case fsproto.ErrFileWriteOnly: // file is write only
return syscall.EACCES
case fsproto.ErrInvalidOffset: // invalid file offset
return syscall.EINVAL
case fsproto.ErrNoRemoveRoot: // refusing to remove root directory
return syscall.EPERM
case fsproto.ErrFileClosed: // cannot perform operation on closed file
return syscall.EBADF
default:
return syscall.EINVAL
}
switch err {
case blefs.FSError{0x02}: // filesystem error
return syscall.EIO // TODO
case blefs.FSError{0x05}: // read-only filesystem
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
case blefs.FSError{-84}: // filesystem is corrupted
return syscall.ENOTRECOVERABLE // TODO
case blefs.FSError{-2}: // no such directory entry
return syscall.ENOENT
case blefs.FSError{-17}: // entry already exists
return syscall.EEXIST
case blefs.FSError{-20}: // entry is not a directory
return syscall.ENOTDIR
case blefs.FSError{-39}: // directory is not empty
return syscall.ENOTEMPTY
case blefs.FSError{-9}: // bad file number
return syscall.EBADF
case blefs.FSError{-27}: // file is too large
return syscall.EFBIG
case blefs.FSError{-22}: // invalid parameter
return syscall.EINVAL
case blefs.FSError{-28}: // no space left on device
return syscall.ENOSPC
case blefs.FSError{-12}: // no more memory available
return syscall.ENOMEM
case blefs.FSError{-61}: // no attr available
return syscall.ENODATA // TODO
case blefs.FSError{-36}: // file name is too long
return syscall.ENAMETOOLONG
case blefs.ErrFileNotExists: // file does not exist
return syscall.ENOENT
case blefs.ErrFileReadOnly: // file is read only
return syscall.EACCES
case blefs.ErrFileWriteOnly: // file is write only
return syscall.EACCES
case blefs.ErrInvalidOffset: // invalid file offset
return syscall.EFAULT // TODO
case blefs.ErrOffsetChanged: // offset has already been changed
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.EIO
return syscall.EIO // TODO
}
-17
View File
@@ -1,17 +0,0 @@
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
+67 -73
View File
@@ -26,16 +26,15 @@ import (
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
"github.com/gen2brain/dlgs"
"github.com/knadh/koanf"
"github.com/mattn/go-isatty"
"go.elara.ws/itd/infinitime"
"go.elara.ws/logger"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
)
var k = koanf.New(".")
@@ -59,43 +58,69 @@ func main() {
if err != nil {
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
opts := infinitime.Options{
OnReconnect: func(dev *infinitime.Device) {
if k.Bool("on.reconnect.setTime") {
// Set time to current time
err = dev.SetTime(time.Now())
if err != nil {
return
}
}
// If config specifies to notify on reconnect
if k.Bool("on.reconnect.notify") {
// Send notification to InfiniTime
err = dev.Notify("itd", "Successfully reconnected")
if err != nil {
return
}
}
// FS must be updated on reconnect
updateFS = true
// Resend weather on reconnect
sendWeatherCh <- struct{}{}
},
opts := &infinitime.Options{
AttemptReconnect: k.Bool("conn.reconnect"),
WhitelistEnabled: k.Bool("conn.whitelist.enabled"),
Whitelist: k.Strings("conn.whitelist.devices"),
OnReqPasskey: onReqPasskey,
Logger: log.Logger,
LogLevel: level,
}
ctx := context.Background()
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(opts)
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") {
// Set time to current time
err = dev.SetTime(time.Now())
if err != nil {
return
}
}
// If config specifies to notify on reconnect
if k.Bool("on.reconnect.notify") {
// Send notification to InfiniTime
err = dev.Notify("itd", "Successfully reconnected")
if err != nil {
return
}
}
// FS must be updated on reconnect
updateFS = true
// Resend weather on reconnect
sendWeatherCh <- struct{}{}
}
// Get firmware version
ver, err := dev.Version()
if err != nil {
@@ -120,88 +145,57 @@ func main() {
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
err = initMusicCtrl(ctx, wg, dev)
err = initMusicCtrl(ctx, dev)
if err != nil {
log.Error("Error initializing music control").Err(err).Send()
}
// Start control socket
err = initCallNotifs(ctx, wg, dev)
err = initCallNotifs(ctx, dev)
if err != nil {
log.Error("Error initializing call notifications").Err(err).Send()
}
// Initialize notification relay
err = initNotifRelay(ctx, wg, dev)
err = initNotifRelay(ctx, dev)
if err != nil {
log.Error("Error initializing notification relay").Err(err).Send()
}
// Initializa weather
err = initWeather(ctx, wg, dev)
err = initWeather(ctx, dev)
if err != nil {
log.Error("Error initializing weather").Err(err).Send()
}
// Initialize metrics collection
err = initMetrics(ctx, wg, dev)
err = initMetrics(ctx, dev)
if err != nil {
log.Error("Error intializing metrics collection").Err(err).Send()
}
// Initialize puremaps integration
err = initPureMaps(ctx, wg, dev)
// Initialize metrics collection
err = initPureMaps(ctx, dev)
if err != nil {
log.Error("Error intializing puremaps integration").Err(err).Send()
}
// Start fuse socket
if k.Bool("fuse.enabled") {
err = startFUSE(ctx, wg, dev)
err = startFUSE(ctx, dev)
if err != nil {
log.Error("Error starting fuse socket").Err(err).Send()
}
}
// Start control socket
err = startSocket(ctx, wg, dev)
err = startSocket(ctx, dev)
if err != nil {
log.Error("Error starting socket").Err(err).Send()
}
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)
// Block forever
select {}
}
func onReqPasskey() (uint32, error) {
+12 -15
View File
@@ -5,9 +5,9 @@ import (
"strings"
"github.com/godbus/dbus/v5"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/utils"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
"go.arsenm.dev/logger/log"
)
const (
@@ -18,7 +18,7 @@ const (
progressProperty = interfaceName + ".progress"
)
func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
// Connect to session bus. This connection is for method calls.
conn, err := utils.NewSessionBusConn(ctx)
if err != nil {
@@ -59,10 +59,7 @@ func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) err
}
}
wg.Add(1)
go func() {
defer wg.Done("pureMaps")
signalCh := make(chan *dbus.Message, 10)
monitorConn.Eavesdrop(signalCh)
@@ -100,7 +97,7 @@ func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) err
continue
}
err = dev.SetNavFlag(infinitime.NavFlag(icon))
err = dev.Navigation.SetFlag(infinitime.NavFlag(icon))
if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue
@@ -113,7 +110,7 @@ func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) err
continue
}
err = dev.SetNavNarrative(narrative)
err = dev.Navigation.SetNarrative(narrative)
if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue
@@ -126,7 +123,7 @@ func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) err
continue
}
err = dev.SetNavManeuverDistance(manDist)
err = dev.Navigation.SetManDist(manDist)
if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue
@@ -139,7 +136,7 @@ func initPureMaps(ctx context.Context, wg WaitGroup, dev *infinitime.Device) err
continue
}
err = dev.SetNavProgress(uint8(progress))
err = dev.Navigation.SetProgress(uint8(progress))
if err != nil {
log.Error("Error setting flag").Err(err).Str("property", member).Send()
continue
@@ -165,7 +162,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err
}
err = dev.SetNavFlag(infinitime.NavFlag(icon))
err = dev.Navigation.SetFlag(infinitime.NavFlag(icon))
if err != nil {
return err
}
@@ -176,7 +173,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err
}
err = dev.SetNavNarrative(narrative)
err = dev.Navigation.SetNarrative(narrative)
if err != nil {
return err
}
@@ -187,7 +184,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err
}
err = dev.SetNavManeuverDistance(manDist)
err = dev.Navigation.SetManDist(manDist)
if err != nil {
return err
}
@@ -198,7 +195,7 @@ func setAll(navigator dbus.BusObject, dev *infinitime.Device) error {
return err
}
return dev.SetNavProgress(uint8(progress))
return dev.Navigation.SetProgress(uint8(progress))
}
// pureMapsExists checks to make sure the PureMaps service exists on the bus
+54 -74
View File
@@ -6,12 +6,12 @@ import (
"path/filepath"
"time"
"go.elara.ws/itd/infinitime"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/logger/log"
_ "modernc.org/sqlite"
)
func initMetrics(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func initMetrics(ctx context.Context, dev *infinitime.Device) error {
// If metrics disabled, return nil
if !k.Bool("metrics.enabled") {
return nil
@@ -47,104 +47,84 @@ func initMetrics(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
return err
}
// Watch heart rate
// If heart rate metrics enabled in config
if k.Bool("metrics.heartRate.enabled") {
err := dev.WatchHeartRate(ctx, func(heartRate uint8, 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 heartRate VALUES (?, ?);", unixTime, heartRate)
})
// Watch heart rate
heartRateCh, err := dev.WatchHeartRate(ctx)
if err != nil {
return err
}
go func() {
// For every heart rate sample
for heartRate := range heartRateCh {
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample and time into database
db.Exec("INSERT INTO heartRate VALUES (?, ?);", unixTime, heartRate)
}
}()
}
// If step count metrics enabled in config
if k.Bool("metrics.stepCount.enabled") {
// Watch step count
err := dev.WatchStepCount(ctx, func(count uint32, err error) {
if err != nil {
return
}
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample and time into database
db.Exec("INSERT INTO stepCount VALUES (?, ?);", unixTime, count)
})
stepCountCh, err := dev.WatchStepCount(ctx)
if err != nil {
return err
}
}
// Watch step count
if k.Bool("metrics.stepCount.enabled") {
err := dev.WatchStepCount(ctx, func(count uint32, err error) {
if err != nil {
// Handle error
return
go func() {
// For every step count sample
for stepCount := range stepCountCh {
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample and time into database
db.Exec("INSERT INTO stepCount VALUES (?, ?);", unixTime, stepCount)
}
// 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 battery level metrics enabled in config
if k.Bool("metrics.battLevel.enabled") {
err := dev.WatchBatteryLevel(ctx, func(battLevel uint8, 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 battLevel VALUES (?, ?);", unixTime, battLevel)
})
// Watch battery level
battLevelCh, err := dev.WatchBatteryLevel(ctx)
if err != nil {
return err
}
go func() {
// For every battery level sample
for battLevel := range battLevelCh {
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample and time into database
db.Exec("INSERT INTO battLevel VALUES (?, ?);", unixTime, battLevel)
}
}()
}
// Watch motion values
// If motion metrics enabled in config
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
unixTime := time.Now().UnixNano()
// Insert sample values and time into database
db.Exec(
"INSERT INTO motion VALUES (?, ?, ?, ?);",
unixTime,
motionVals.X,
motionVals.Y,
motionVals.Z,
)
})
// Watch motion values
motionCh, err := dev.WatchMotion(ctx)
if err != nil {
return err
}
go func() {
// For every motion sample
for motionVals := range motionCh {
// Get current time
unixTime := time.Now().UnixNano()
// Insert sample values and time into database
db.Exec(
"INSERT INTO motion VALUES (?, ?, ?, ?);",
unixTime,
motionVals.X,
motionVals.Y,
motionVals.Z,
)
}
}()
}
wg.Add(1)
go func() {
defer wg.Done("metrics")
<-ctx.Done()
db.Close()
}()
log.Info("Initialized metrics collection").Send()
return nil
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"sync"
"github.com/godbus/dbus/v5"
"go.elara.ws/itd/internal/utils"
"go.arsenm.dev/itd/internal/utils"
)
var (
+30 -30
View File
@@ -21,13 +21,13 @@ package main
import (
"context"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/mpris"
"go.elara.ws/itd/translit"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/mpris"
"go.arsenm.dev/itd/translit"
"go.arsenm.dev/logger/log"
)
func initMusicCtrl(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func initMusicCtrl(ctx context.Context, dev *infinitime.Device) error {
mpris.Init(ctx)
maps := k.Strings("notifs.translit.use")
@@ -38,42 +38,42 @@ func initMusicCtrl(ctx context.Context, wg WaitGroup, dev *infinitime.Device) er
if !firmwareUpdating {
switch ct {
case mpris.ChangeTypeStatus:
dev.SetMusicStatus(val == "Playing")
dev.Music.SetStatus(val == "Playing")
case mpris.ChangeTypeTitle:
dev.SetMusicTrack(newVal)
dev.Music.SetTrack(newVal)
case mpris.ChangeTypeAlbum:
dev.SetMusicAlbum(newVal)
dev.Music.SetAlbum(newVal)
case mpris.ChangeTypeArtist:
dev.SetMusicArtist(newVal)
dev.Music.SetArtist(newVal)
}
}
})
// Watch for music events
err := dev.WatchMusicEvents(ctx, func(event infinitime.MusicEvent, err error) {
if err != nil {
log.Error("Music event error").Err(err).Send()
}
// Perform appropriate action based on event
switch event {
case infinitime.MusicEventPlay:
mpris.Play()
case infinitime.MusicEventPause:
mpris.Pause()
case infinitime.MusicEventNext:
mpris.Next()
case infinitime.MusicEventPrev:
mpris.Prev()
case infinitime.MusicEventVolUp:
mpris.VolUp(uint(k.Int("music.vol.interval")))
case infinitime.MusicEventVolDown:
mpris.VolDown(uint(k.Int("music.vol.interval")))
}
})
musicEvtCh, err := dev.Music.WatchEvents()
if err != nil {
return err
}
go func() {
// For every music event received
for musicEvt := range musicEvtCh {
// Perform appropriate action based on event
switch musicEvt {
case infinitime.MusicEventPlay:
mpris.Play()
case infinitime.MusicEventPause:
mpris.Pause()
case infinitime.MusicEventNext:
mpris.Next()
case infinitime.MusicEventPrev:
mpris.Prev()
case infinitime.MusicEventVolUp:
mpris.VolUp(uint(k.Int("music.vol.interval")))
case infinitime.MusicEventVolDown:
mpris.VolDown(uint(k.Int("music.vol.interval")))
}
}
}()
// Log completed initialization
log.Info("Initialized InfiniTime music controls").Send()
+39 -47
View File
@@ -23,13 +23,13 @@ import (
"fmt"
"github.com/godbus/dbus/v5"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/utils"
"go.elara.ws/itd/translit"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
"go.arsenm.dev/itd/translit"
"go.arsenm.dev/logger/log"
)
func initNotifRelay(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func initNotifRelay(ctx context.Context, dev *infinitime.Device) error {
// Connect to dbus session bus
bus, err := utils.NewSessionBusConn(ctx)
if err != nil {
@@ -54,51 +54,43 @@ func initNotifRelay(ctx context.Context, wg WaitGroup, dev *infinitime.Device) e
// Send events to channel
bus.Eavesdrop(notifCh)
wg.Add(1)
go func() {
defer wg.Done("notifRelay")
// For every event sent to channel
for {
select {
case v := <-notifCh:
// If firmware is updating, skip
if firmwareUpdating {
continue
}
// If body does not contain 5 elements, skip
if len(v.Body) < 5 {
continue
}
// Get requred fields
sender, summary, body := v.Body[0].(string), v.Body[3].(string), v.Body[4].(string)
// If fields are ignored in config, skip
if ignored(sender, summary, body) {
continue
}
maps := k.Strings("notifs.translit.use")
translit.Transliterators["custom"] = translit.Map(k.Strings("notifs.translit.custom"))
sender = translit.Transliterate(sender, maps...)
summary = translit.Transliterate(summary, maps...)
body = translit.Transliterate(body, maps...)
var msg string
// If summary does not exist, set message to body.
// If it does, set message to summary, two newlines, and then body
if summary == "" {
msg = body
} else {
msg = fmt.Sprintf("%s\n\n%s", summary, body)
}
dev.Notify(sender, msg)
case <-ctx.Done():
bus.Close()
return
for v := range notifCh {
// If firmware is updating, skip
if firmwareUpdating {
continue
}
// If body does not contain 5 elements, skip
if len(v.Body) < 5 {
continue
}
// Get requred fields
sender, summary, body := v.Body[0].(string), v.Body[3].(string), v.Body[4].(string)
// If fields are ignored in config, skip
if ignored(sender, summary, body) {
continue
}
maps := k.Strings("notifs.translit.use")
translit.Transliterators["custom"] = translit.Map(k.Strings("notifs.translit.custom"))
sender = translit.Transliterate(sender, maps...)
summary = translit.Transliterate(summary, maps...)
body = translit.Transliterate(body, maps...)
var msg string
// If summary does not exist, set message to body.
// If it does, set message to summary, two newlines, and then body
if summary == "" {
msg = body
} else {
msg = fmt.Sprintf("%s\n\n%s", summary, body)
}
dev.Notify(sender, msg)
}
}()
+150 -207
View File
@@ -19,7 +19,6 @@
package main
import (
"archive/zip"
"context"
"errors"
"io"
@@ -28,10 +27,11 @@ import (
"path/filepath"
"time"
"go.elara.ws/drpc/muxserver"
"go.elara.ws/itd/infinitime"
"go.elara.ws/itd/internal/rpc"
"go.elara.ws/logger/log"
"go.arsenm.dev/drpc/muxserver"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/blefs"
"go.arsenm.dev/itd/internal/rpc"
"go.arsenm.dev/logger/log"
"storj.io/drpc/drpcmux"
)
@@ -41,7 +41,7 @@ var (
ErrDFUInvalidUpgType = errors.New("invalid upgrade type")
)
func startSocket(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
func startSocket(ctx context.Context, dev *infinitime.Device) error {
// Make socket directory if non-existant
err := os.MkdirAll(filepath.Dir(k.String("socket.path")), 0o755)
if err != nil {
@@ -60,7 +60,11 @@ func startSocket(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
return err
}
fs := dev.FS()
fs, err := dev.FS()
if err != nil {
log.Warn("Error getting BLE filesystem").Err(err).Send()
}
mux := drpcmux.New()
err = rpc.DRPCRegisterITD(mux, &ITD{dev})
@@ -73,13 +77,10 @@ func startSocket(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
return err
}
log.Info("Starting control socket").Str("path", k.String("socket.path")).Send()
go muxserver.New(mux).Serve(ctx, ln)
wg.Add(1)
go func() {
defer wg.Done("socket")
muxserver.New(mux).Serve(ctx, ln)
}()
// Log socket start
log.Info("Started control socket").Str("path", k.String("socket.path")).Send()
return nil
}
@@ -94,29 +95,19 @@ func (i *ITD) HeartRate(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, erro
}
func (i *ITD) WatchHeartRate(_ *rpc.Empty, s rpc.DRPCITD_WatchHeartRateStream) error {
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
}
})
heartRateCh, err := i.dev.WatchHeartRate(s.Context())
if err != nil {
return err
}
select {
case <-errCh:
return err
case <-s.Context().Done():
return nil
for heartRate := range heartRateCh {
err = s.Send(&rpc.IntResponse{Value: uint32(heartRate)})
if err != nil {
return err
}
}
return nil
}
func (i *ITD) BatteryLevel(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) {
@@ -125,29 +116,19 @@ func (i *ITD) BatteryLevel(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, e
}
func (i *ITD) WatchBatteryLevel(_ *rpc.Empty, s rpc.DRPCITD_WatchBatteryLevelStream) error {
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
}
})
battLevelCh, err := i.dev.WatchBatteryLevel(s.Context())
if err != nil {
return err
}
select {
case <-errCh:
return err
case <-s.Context().Done():
return nil
for battLevel := range battLevelCh {
err = s.Send(&rpc.IntResponse{Value: uint32(battLevel)})
if err != nil {
return err
}
}
return nil
}
func (i *ITD) Motion(_ context.Context, _ *rpc.Empty) (*rpc.MotionResponse, error) {
@@ -160,33 +141,23 @@ func (i *ITD) Motion(_ context.Context, _ *rpc.Empty) (*rpc.MotionResponse, erro
}
func (i *ITD) WatchMotion(_ *rpc.Empty, s rpc.DRPCITD_WatchMotionStream) error {
errCh := make(chan error)
err := i.dev.WatchMotion(s.Context(), func(motion infinitime.MotionValues, err error) {
if err != nil {
errCh <- err
return
}
err = s.Send(&rpc.MotionResponse{
X: int32(motion.X),
Y: int32(motion.Y),
Z: int32(motion.Z),
})
if err != nil {
errCh <- err
}
})
motionValsCh, err := i.dev.WatchMotion(s.Context())
if err != nil {
return err
}
select {
case <-errCh:
return err
case <-s.Context().Done():
return nil
for motionVals := range motionValsCh {
err = s.Send(&rpc.MotionResponse{
X: int32(motionVals.X),
Y: int32(motionVals.Y),
Z: int32(motionVals.Z),
})
if err != nil {
return err
}
}
return nil
}
func (i *ITD) StepCount(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, error) {
@@ -195,29 +166,19 @@ func (i *ITD) StepCount(_ context.Context, _ *rpc.Empty) (*rpc.IntResponse, erro
}
func (i *ITD) WatchStepCount(_ *rpc.Empty, s rpc.DRPCITD_WatchStepCountStream) error {
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
}
})
stepCountCh, err := i.dev.WatchStepCount(s.Context())
if err != nil {
return err
}
select {
case <-errCh:
return err
case <-s.Context().Done():
return nil
for stepCount := range stepCountCh {
err = s.Send(&rpc.IntResponse{Value: stepCount})
if err != nil {
return err
}
}
return nil
}
func (i *ITD) Version(_ context.Context, _ *rpc.Empty) (*rpc.StringResponse, error) {
@@ -242,34 +203,39 @@ func (i *ITD) WeatherUpdate(context.Context, *rpc.Empty) (*rpc.Empty, error) {
return &rpc.Empty{}, nil
}
func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_FirmwareUpgradeStream) (err error) {
var fwimg, initpkt *os.File
func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_FirmwareUpgradeStream) error {
i.dev.DFU.Reset()
switch data.Type {
case rpc.FirmwareUpgradeRequest_Archive:
fwimg, initpkt, err = extractDFU(data.Files[0])
// If less than one file, return error
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 {
return err
}
case rpc.FirmwareUpgradeRequest_Files:
// If less than two files, return error
if len(data.Files) < 2 {
return ErrDFUNotEnoughFiles
}
// If first file is not init packet, return error
if filepath.Ext(data.Files[0]) != ".dat" {
return ErrDFUInvalidFile
}
// If second file is not firmware image, return error
if filepath.Ext(data.Files[1]) != ".bin" {
return ErrDFUInvalidFile
}
initpkt, err = os.Open(data.Files[0])
if err != nil {
return err
}
fwimg, err = os.Open(data.Files[1])
// Load individual DFU files
err := i.dev.DFU.LoadFiles(data.Files[0], data.Files[1])
if err != nil {
return err
}
@@ -277,33 +243,38 @@ func (i *ITD) FirmwareUpgrade(data *rpc.FirmwareUpgradeRequest, s rpc.DRPCITD_Fi
return ErrDFUInvalidUpgType
}
defer os.Remove(fwimg.Name())
defer os.Remove(initpkt.Name())
defer fwimg.Close()
defer initpkt.Close()
firmwareUpdating = true
defer func() { firmwareUpdating = false }()
return i.dev.UpgradeFirmware(infinitime.DFUOptions{
InitPacket: initpkt,
FirmwareImage: fwimg,
ProgressFunc: func(sent, received, total uint32) {
go func() {
for event := range i.dev.DFU.Progress() {
_ = s.Send(&rpc.DFUProgress{
Sent: int64(sent),
Recieved: int64(received),
Total: int64(total),
Sent: int64(event.Sent),
Recieved: int64(event.Received),
Total: event.Total,
})
},
})
}
firmwareUpdating = false
}()
// Set firmwareUpdating
firmwareUpdating = true
// Start DFU
err := i.dev.DFU.Start()
if err != nil {
firmwareUpdating = false
return err
}
return nil
}
type FS struct {
dev *infinitime.Device
fs *infinitime.FS
fs *blefs.FS
}
func (fs *FS) RemoveAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths {
err := fs.fs.RemoveAll(path)
if err != nil {
@@ -314,6 +285,7 @@ func (fs *FS) RemoveAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, e
}
func (fs *FS) Remove(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths {
err := fs.fs.Remove(path)
if err != nil {
@@ -324,10 +296,12 @@ func (fs *FS) Remove(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, erro
}
func (fs *FS) Rename(_ context.Context, req *rpc.RenameRequest) (*rpc.Empty, error) {
fs.updateFS()
return &rpc.Empty{}, fs.fs.Rename(req.From, req.To)
}
func (fs *FS) MkdirAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths {
err := fs.fs.MkdirAll(path)
if err != nil {
@@ -338,6 +312,7 @@ func (fs *FS) MkdirAll(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, er
}
func (fs *FS) Mkdir(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error) {
fs.updateFS()
for _, path := range req.Paths {
err := fs.fs.Mkdir(path)
if err != nil {
@@ -348,6 +323,8 @@ func (fs *FS) Mkdir(_ context.Context, req *rpc.PathsRequest) (*rpc.Empty, error
}
func (fs *FS) ReadDir(_ context.Context, req *rpc.PathRequest) (*rpc.DirResponse, error) {
fs.updateFS()
entries, err := fs.fs.ReadDir(req.Path)
if err != nil {
return nil, err
@@ -369,6 +346,8 @@ func (fs *FS) ReadDir(_ context.Context, req *rpc.PathRequest) (*rpc.DirResponse
}
func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error {
fs.updateFS()
localFile, err := os.Open(req.Source)
if err != nil {
return err
@@ -384,12 +363,15 @@ func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error
return err
}
remoteFile.ProgressFunc = func(transferred, total uint32) {
_ = s.Send(&rpc.TransferProgress{
Total: total,
Sent: transferred,
})
}
go func() {
// For every progress event
for sent := range remoteFile.Progress() {
_ = s.Send(&rpc.TransferProgress{
Total: remoteFile.Size(),
Sent: sent,
})
}
}()
io.Copy(remoteFile, localFile)
localFile.Close()
@@ -399,6 +381,8 @@ func (fs *FS) Upload(req *rpc.TransferRequest, s rpc.DRPCFS_UploadStream) error
}
func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) error {
fs.updateFS()
localFile, err := os.Create(req.Destination)
if err != nil {
return err
@@ -412,12 +396,15 @@ func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) er
defer localFile.Close()
defer remoteFile.Close()
remoteFile.ProgressFunc = func(transferred, total uint32) {
_ = s.Send(&rpc.TransferProgress{
Total: total,
Sent: transferred,
})
}
go func() {
// For every progress event
for sent := range remoteFile.Progress() {
_ = s.Send(&rpc.TransferProgress{
Total: remoteFile.Size(),
Sent: sent,
})
}
}()
_, err = io.Copy(localFile, remoteFile)
if err != nil {
@@ -428,86 +415,42 @@ func (fs *FS) Download(req *rpc.TransferRequest, s rpc.DRPCFS_DownloadStream) er
}
func (fs *FS) LoadResources(req *rpc.PathRequest, s rpc.DRPCFS_LoadResourcesStream) error {
return infinitime.LoadResources(req.Path, fs.fs, func(evt infinitime.ResourceLoadProgress) {
_ = s.Send(&rpc.ResourceLoadProgress{
resFl, err := os.Open(req.Path)
if err != nil {
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,
Total: int64(evt.Total),
Sent: int64(evt.Transferred),
Total: evt.Total,
Sent: evt.Sent,
Operation: rpc.ResourceLoadProgress_Operation(evt.Operation),
})
})
}
func extractDFU(path string) (fwimg, initpkt *os.File, err error) {
zipReader, err := zip.OpenReader(path)
if err != nil {
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
}
if err != nil {
return err
}
}
if fwimg == nil || initpkt == nil {
return nil, nil, errors.New("invalid dfu archive")
}
return fwimg, initpkt, nil
return nil
}
func (fs *FS) updateFS() {
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
@@ -1,16 +0,0 @@
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()
}
+98 -62
View File
@@ -4,14 +4,16 @@ import (
"context"
"encoding/json"
"fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"go.elara.ws/itd/infinitime"
"go.elara.ws/logger/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/weather"
"go.arsenm.dev/logger/log"
)
// METResponse represents a response from
@@ -30,7 +32,7 @@ type METData struct {
Instant struct {
Details struct {
AirPressure float32 `json:"air_pressure_at_sea_level"`
Temperature float32 `json:"air_temperature"`
AirTemperature float32 `json:"air_temperature"`
DewPoint float32 `json:"dew_point_temperature"`
CloudAreaFraction float32 `json:"cloud_area_fraction"`
FogAreaFraction float32 `json:"fog_area_fraction"`
@@ -48,12 +50,6 @@ type METData struct {
PrecipitationAmount float32 `json:"precipitation_amount"`
}
} `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
@@ -65,14 +61,7 @@ type OSMData []struct {
var sendWeatherCh = make(chan struct{}, 1)
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 {
func initWeather(ctx context.Context, dev *infinitime.Device) error {
if !k.Bool("weather.enabled") {
return nil
}
@@ -85,24 +74,14 @@ func initWeather(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
timer := time.NewTimer(time.Hour)
wg.Add(1)
go func() {
defer wg.Done("weather")
for {
select {
case _, ok := <-ctx.Done():
if !ok {
return
}
default:
}
// Attempt to get weather
data, err := getWeather(ctx, lat, lon)
if err != nil {
log.Warn("Error getting weather data").Err(err).Send()
// Wait 15 minutes before retrying
sleepCtx(ctx, 15*time.Minute)
time.Sleep(15 * time.Minute)
continue
}
@@ -110,28 +89,81 @@ func initWeather(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
current := data.Properties.Timeseries[0]
currentData := current.Data.Instant.Details
icon := parseSymbol(current.Data.NextHour.Summary.SymbolCode)
if icon == infinitime.WeatherIconClear {
switch {
case currentData.CloudAreaFraction > 50:
icon = infinitime.WeatherIconHeavyClouds
case currentData.CloudAreaFraction == 50:
icon = infinitime.WeatherIconClouds
case currentData.CloudAreaFraction > 0:
icon = infinitime.WeatherIconFewClouds
}
}
err = dev.SetCurrentWeather(infinitime.CurrentWeather{
Time: time.Now(),
CurrentTemp: currentData.Temperature,
MaxTemp: current.Data.Next6Hours.Details.MaxTemp,
MinTemp: current.Data.Next6Hours.Details.MinTemp,
Location: k.String("weather.location"),
Icon: icon,
// Add temperature event
err = dev.AddWeatherEvent(weather.TemperatureEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypeTemperature,
time.Hour,
),
Temperature: int16(round(currentData.AirTemperature * 100)),
DewPoint: int16(round(currentData.DewPoint)),
})
if err != nil {
log.Error("Error setting weather").Err(err).Send()
log.Error("Error adding temperature event").Err(err).Send()
}
// Add precipitation event
err = dev.AddWeatherEvent(weather.PrecipitationEvent{
TimelineHeader: weather.NewHeader(
weather.EventTypePrecipitation,
time.Hour,
),
Type: parseSymbol(current.Data.NextHour.Summary.SymbolCode),
Amount: uint8(round(current.Data.NextHour.Details.PrecipitationAmount)),
})
if err != nil {
log.Error("Error adding precipitation event").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
@@ -142,8 +174,6 @@ func initWeather(ctx context.Context, wg WaitGroup, dev *infinitime.Device) erro
select {
case <-timer.C:
case <-sendWeatherCh:
case <-ctx.Done():
return
}
}
}()
@@ -159,7 +189,6 @@ func getLocation(ctx context.Context, loc string) (lat, lon float64, err error)
if err != nil {
return
}
req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.elara.ws/Elara6331/itd", strings.TrimSpace(version)))
res, err := http.DefaultClient.Do(req)
if err != nil {
return
@@ -211,7 +240,7 @@ func getWeather(ctx context.Context, lat, lon float64) (*METResponse, error) {
}
// Set identifying user agent as per NMI requirements
req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.elara.ws/Elara6331/itd", strings.TrimSpace(version)))
req.Header.Set("User-Agent", fmt.Sprintf("ITD/%s gitea.arsenm.dev/Arsen6331/itd", version))
// Perform request
res, err := http.DefaultClient.Do(req)
@@ -229,19 +258,26 @@ func getWeather(ctx context.Context, lat, lon float64) (*METResponse, error) {
return out, nil
}
// parseSymbol determines what weather icon a symbol code codes for.
func parseSymbol(symCode string) infinitime.WeatherIcon {
// parseSymbol determines what type of precipitation a symbol code
// codes for.
func parseSymbol(symCode string) weather.PrecipitationType {
switch {
case strings.Contains(symCode, "lightrain"):
return infinitime.WeatherIconRain
return weather.PrecipitationTypeRain
case strings.Contains(symCode, "rain"):
return infinitime.WeatherIconCloudsWithRain
case strings.Contains(symCode, "snow"),
strings.Contains(symCode, "sleet"):
return infinitime.WeatherIconSnow
case strings.Contains(symCode, "thunder"):
return infinitime.WeatherIconThunderstorm
return weather.PrecipitationTypeRain
case strings.Contains(symCode, "snow"):
return weather.PrecipitationTypeSnow
case strings.Contains(symCode, "sleet"):
return weather.PrecipitationTypeSleet
case strings.Contains(symCode, "snow"):
return weather.PrecipitationTypeSnow
default:
return infinitime.WeatherIconClear
return weather.PrecipitationTypeNone
}
}
// round rounds 32-bit floats to 32-bit integers
func round(f float32) int32 {
return int32(math.Round(float64(f)))
}