Compare commits

...

40 Commits
2.1.6 ... 2.2.1

Author SHA1 Message Date
wj-xiao
e4367f0b69 update CHANGELOG 2025-02-21 19:20:19 +08:00
wj-xiao
f973316f8f Fix cdrom translation 2025-02-21 15:04:39 +08:00
wj-xiao
b5e48a07e8 fix: mounted image were not being detected correctly
perf: add support for CD-ROM mode when mounting image
perf: add a loading state during login
perf: add changelog link in settings
2025-02-21 14:52:48 +08:00
wenjie
9c0c50140b Merge pull request #334 from S33G/i18n/unexpected-error
Make unexpected error localizable
2025-02-20 17:01:44 +08:00
wenjie
33d0eb3297 Merge pull request #328 from Jonher937/update-available-string
Improve English strings in web interface
2025-02-20 17:00:39 +08:00
wenjie
922a7772b8 Merge pull request #327 from Jonher937/spelling
Fix spelling
2025-02-20 16:59:41 +08:00
wenjie
38ddd89e56 Merge pull request #326 from Jonher937/fix-formatted-logging
Use format logger where needed
2025-02-20 16:40:55 +08:00
wenjie
b664411992 Merge pull request #325 from Jonher937/cleanup-fmt
remove unecessary fmt
2025-02-20 16:37:05 +08:00
wenjie
7a301f588a Merge pull request #323 from polyzium/ru-translaton-fix
Update (and fix) Russian translation
2025-02-20 16:36:09 +08:00
wenjie
e5ebb36f78 Merge pull request #322 from Stoufiler/main
Fix French translation
2025-02-20 16:35:35 +08:00
wenjie
01e4802247 Merge pull request #320 from ChokunPlayZ/main
Add Thai Translation
2025-02-20 16:34:55 +08:00
Yanavut Rojanapron
66b1ea26c5 Merge branch 'sipeed:main' into main 2025-02-20 15:09:43 +07:00
s33g
fbf2531dbb Make unexpected error localizable 2025-02-20 00:08:18 +00:00
Chokun Rojanapron
3debfc4cf1 lang: added Thai (ภาษาไทย)
and complete translation.
2025-02-19 22:17:19 +07:00
BuGu
b35542a446 Update README.md 2025-02-19 14:20:35 +08:00
BuGu
dcf5bc99dd Merge pull request #329 from S33G/patch-1
Readme improvements
2025-02-19 11:44:44 +08:00
seeg
f984b9f870 Update README.md 2025-02-19 01:06:36 +00:00
Jonathan Herlin
8c547b8baa Improve English strings 2025-02-18 23:52:53 +01:00
Jonathan Herlin
a265eaef15 Fix spelling 2025-02-18 23:10:03 +01:00
Jonathan Herlin
f8eaa7f615 Use format logger where needed 2025-02-18 23:08:12 +01:00
Jonathan Herlin
c47fc483a2 remove unecessary fmt 2025-02-18 22:59:28 +01:00
BuGu
64254052c0 Add kvm_vision_test 2025-02-18 19:02:13 +08:00
BuGu
b99a8358a7 Add components/vision, replace the MaixCDK vision 2025-02-18 19:01:41 +08:00
BuGu
ed0a6f6681 Add components/kvm_mmf, which will compile to libkvm_mmf.so 2025-02-18 19:00:39 +08:00
BuGu
055df124b3 Add components/kvm, which will compile to libkvm.so 2025-02-18 19:00:16 +08:00
BuGu
06097068a2 Update kvm_system
Fix DNS issues
2025-02-18 18:59:49 +08:00
polyzium
ff5398ed67 Update (and fix) Russian translation
Add missing keys to the ru.ts file as well as fix errors introduced by machine translation.
2025-02-18 12:58:25 +03:00
wj-xiao
c8dcdf7921 update README and CHANGELOG 2025-02-18 09:57:51 +08:00
Stoufiler
e09a0b14e0 Fix French translation 2025-02-18 00:38:11 +01:00
wj-xiao
0dbf8c007f fix: adjust condition for password update notification
fix: correct formatting error in language file
perf: increase update wait time to 10s (from 6s)
refactor: update libkvm.so and libkvm_mmf.so libraries
2025-02-17 16:45:54 +08:00
wenjie
a24c1b0cef Merge pull request #318 from bilogic/main
refactored for readability
2025-02-17 10:24:03 +08:00
wenjie
42dc6ad079 Merge pull request #317 from protonchang/zhtw-translation
Update zhTW Translation
2025-02-17 10:23:25 +08:00
wenjie
d670cf19a1 Merge pull request #315 from forumi0721/main
Update korean translations
2025-02-17 10:22:42 +08:00
wenjie
7fe7c49e65 Merge pull request #312 from li20034/fix_password_updated_check
Fix password updated check
2025-02-17 10:21:33 +08:00
bilogic
5c1e617862 refactored for readability 2025-02-16 14:33:24 +08:00
protonchang
9ff9d59aa5 Update zhTW Translation 2025-02-16 02:45:18 +08:00
forumi0721
7556d154d3 Update korean translations 2025-02-16 02:06:39 +09:00
Zonggao Li
df808da5d6 Fix password updated check 2025-02-14 23:50:44 -08:00
Caize Wu
06bd869fb1 Update README.md 2025-02-14 15:48:25 +08:00
wj-xiao
7343ca3811 update README and CHANGELOG 2025-02-14 14:35:42 +08:00
64 changed files with 13976 additions and 402 deletions

74
CHANGELOG.md Normal file
View File

@@ -0,0 +1,74 @@
## 2.2.1 [b5e48a0](https://github.com/sipeed/NanoKVM/commit/b5e48a07e82df3aedd60442342ae50b95684a697) (2025-02-21)
* fix: mounted image were not being detected correctly
* perf: add support for CD-ROM mode when mounting image (thanks to [@scpcom](https://github.com/scpcom))
* perf: add a loading state during login
* perf: add changelog link in settings
* perf: update translation and cleanup the code (thanks to [@ChokunPlayZ](https://github.com/ChokunPlayZ) [@Stoufiler](https://github.com/Stoufiler) [@polyzium](https://github.com/polyzium) [@Jonher937](https://github.com/Jonher937) [@S33G](https://github.com/S33G))
## 2.2.0 [0dbf8c0](https://github.com/sipeed/NanoKVM/commit/0dbf8c007f2d0183d0f0601c3da6d3c3fccd8b31) (2025-02-17)
NanoKVM [Image v1.4.0](https://github.com/sipeed/NanoKVM/releases/tag/v1.4.0) has been released!
> Please refer to the [wiki](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/introduction.html) for more details about the image and application.
* fix: improve password update notification logic (thanks to [@li20034](https://github.com/li20034))
* perf: increase update wait time to 10s (from 6s)
* perf: update Korean translation (thanks to [@forumi0721](https://github.com/forumi0721))
* perf: update Traditional Chinese translation (thanks to [@protonchang](https://github.com/protonchang))
* refactor: update `libkvm.so` and `libkvm_mmf.so` libraries
## 2.1.6 [6eb4a4e](https://github.com/sipeed/NanoKVM/commit/6eb4a4ea6254f465a47f9881d13934c686649061) (2025-02-14)
* feat: support downloading image from online URL (thanks to [@Itxaka](https://github.com/Itxaka))
* feat: add keyboard shortcut `Ctrl+Alt+Del` (thanks to [@CaffeeLake](https://github.com/CaffeeLake))
* fix: fix the CSRF issue
* perf: add an option to configure custom ICE servers (thanks to [@VMFortress](https://github.com/VMFortress))
* perf: removed unnecessary modifications to DNS configuration
* perf: add an SSH enable/disable toggle in the web UI
* perf: add a Tailscale enable/disable toggle in the web UI
* perf: download Tailscale installation package from the official source
* perf: automatic enable/disable GOMEMLIMIT on tailscale start/stop
* perf: add JWT configuration
* secretKey: customize secret key. If empty, generated a random 64-byte secret key by default
* refreshTokenDuration: customize token expiration time
* revokeTokensOnLogout: invalidate all JWT tokens on logout
* perf: implement secure password storage using bcrypt hashing
* perf: implement integrity checks for online updates
* refactor: refactor HDMI module and remove the dependency `libmaixcam_lib.so`
* refactor: web terminal use pty instead of SSH
* refactor: move Tailscale APIs from the `network` module to the `extensions` module
## 2.1.5 [85f6447](https://github.com/sipeed/NanoKVM/commit/85f6447a16cc2591c6459b7d3dfda4d4cb75e98c) (2025-01-14)
* feat: add HDMI reset for NanoKVM-PCIe
* fix: remove unnecessary lock acquisition during HID reset
* refactor: refactor Tailscale
## 2.1.4 [d7ca7c4](https://github.com/sipeed/NanoKVM/commit/d7ca7c453d821ad099bf79b463969419041279cb) (2025-01-10)
* feat: support configuring OLED sleep settings
* feat: support setting the `GOMEMLIMIT` environment variable
* fix: fix Wi-Fi configuration
* perf: password changes now update both the web user and the system root user
* perf: add MAC address verification for Wake-on-LAN
* refactor: a lot update to web UI
* refactor: refactor Tailscale
## 2.1.3 [26078fe](https://github.com/sipeed/NanoKVM/commit/26078fe46e43d4543d7b09901b4992e4fbe4f01f) (2024-12-27)
* feat: add API to retrieve Wi-Fi information
* fix: fix keyboard modifier keys
* fix: update keyboard and mouse HID codes
* fix: update hardware version information
## 2.1.2 [5a39562](https://github.com/sipeed/NanoKVM/commit/5a39562f2d32695933f4e7e86866136236cc9903) (2024-12-04)
* feat: add hardware version to configuration
* feat: add Wi-Fi configuration support for NanoKVM-PCIe
* perf: update web UI
* chore: add dependency libraries
## 2.1.1 [74a303b](https://github.com/sipeed/NanoKVM/commit/74a303bd5cbb58f9d8ddd81abaaf4919dbbfb71b) (2024-11-06)
* feat: support h264

View File

@@ -15,19 +15,25 @@ English | [中文](./README_ZH.md) | [日本語](./README_JA.md)
</div>
> Your NanoKVM Power by RISC-V !
> Your NanoKVM is powered by [RISC-V](https://en.wikipedia.org/wiki/RISC-V)!
> If you have any issues or suggestions, creating issue here, or tell us with [MaixHub Discussion](https://maixhub.com/discussion/nanokvm).
## Opensource & Contribution
### Update 2025.02.19
All components of NanoKVM were open-sourced, including the [front-end](https://github.com/sipeed/NanoKVM/tree/main/web), [back-end](https://github.com/sipeed/NanoKVM/tree/main/server), [kvm_vision](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm), [kvm_mmf](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm_mmf), [kvm_system](https://github.com/sipeed/NanoKVM/tree/main/support), the [kvmapp update package](https://github.com/sipeed/NanoKVM-System/tree/main/kvmapp), [system sdk](https://github.com/sipeed/LicheeRV-Nano-Build/tree/NanoKVM), and [packaging methods](https://github.com/sipeed/LicheeRV-Nano-Build/blob/NanoKVM/kvm/NanoKVM_img.sh).
### Update 2025.02.14
Newest APP v2.1.6 add most security enhancements&bug fix, recommend regular users upgrade to this version or newer.
### Update 2025.02.05
For users concerned about security, you can read this issue: https://github.com/sipeed/NanoKVM/issues/301 , you will find explanations addressing all security concerns.
GitHub, as an open and transparent platform, provides a space for discussing so-called "backdoor" concerns. Open-source makes products more secure!
### Update 2024.10.18
We have opensourced the backend code in 10.8, and thank you for Civil PR it immediately, we will give you newest NanoKVM-PCIe as a reward!
The mid-Oct batch is starting testing and packaging, most order before 10.1 will ship out next and next next week.
We have open-sourced the backend code in 10.8, and thank you for civil PR it immediately, we will give you newest NanoKVM-PCIe as a reward!
The mid-Oct batch is starting testing and packaging, most orders before 10.1 will ship out next and next next week.
The aliexpress shipping date is change to Dec since 10.1, it is a conservative delivery time. the order in Oct. should send out during 11.15~12.15.
## Introduction
@@ -37,33 +43,33 @@ The Lichee NanoKVM is available in two versions:
NanoKVM Lite is a basic configuration that is suitable for individual users with certain DIY capabilities and enterprise users with bulk requirements.
The NanoKVM Full is a full version with a sophisticated case and complete accessories, as well as a built-in system mirror card that is ready to use at boot, and is recommended for individual users.
![](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg)
![NanoKVM Disassembled](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg)
## Tech Specs
| Products | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 |
| --- | --- | --- | --- |
| Compute Units | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| Resolution | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
| Video encoding | MJPEG, H264(developing) | MJPEG, H264(developing) | MJPEG, H264 |
| Video delay | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| Virtual HID | ✓ | ✓ | ✓ |
| Virtual CD-ROM | ✓ | ✓ | ✓ |
| IPMI | ✓ | ✓ | ✓ |
| Wake-on-LAN | ✓ | ✓ | ✓ |
| ETH | 100M/10M | 100M/10M | 1000M/100M/10M |
| ATX power control | None,Users can connect by their own | USB interface IO control board | RJ-45 interface IO control board |
| OLED | None,Users can connect by their own | 128x64 0.96" white | 128x32 0.91" white |
| UART | 2 | 2 | 1 |
| TF Card | None | ✓ | ✓ |
| Expansion | None | PoE | WiFi/LTE |
| Power consumption | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V |
| Power input | PC USB can be powered | PC USB or auxiliary power supply | DC 5V 3A power supply is required |
| Cooling | Silent fanless | Silent fanless | Fan cooling |
| Size | 23x37x15mm <br> ~1/30 PiKVM V4 size | 40x36x36mm <br/> ~1/7 PiKVM V4 size | 120x68x44mm |
| Products | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 |
|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |
| Compute Units | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| Resolution | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
| Video encoding | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
| Video delay | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| Virtual HID | ✓ | ✓ | ✓ |
| Virtual CD-ROM | ✓ | ✓ | ✓ |
| IPMI | ✓ | ✓ | ✓ |
| Wake-on-LAN | ✓ | ✓ | ✓ |
| ETH | 100M/10M | 100M/10M | 1000M/100M/10M |
| ATX power control | None,Users can connect by their own | USB interface IO control board | RJ-45 interface IO control board |
| OLED | None,Users can connect by their own | 128x64 0.96" white | 128x32 0.91" white |
| UART | 2 | 2 | 1 |
| TF Card | None | ✓ | ✓ |
| Expansion | None | PoE | WiFi/LTE |
| Power consumption | 0.2A@5V | 0.2A@5V | Peak 2.6A@5V |
| Power input | PC USB can be powered | PC USB or auxiliary power supply | DC 5V 3A power supply is required |
| Cooling | Silent fanless | Silent fanless | Fan cooling |
| Size | 23x37x15mm ~1/30 PiKVM V4 size | 40x36x36mm ~1/7 PiKVM V4 size | 120x68x44mm |
![](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg)
![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg)
## Hardware platform NanoKVM
@@ -73,7 +79,7 @@ The NanoKVM Lite is constructed by the LicheeRV Nano and HDMItoCSI board, and th
The NanoKVM image is built on LicheeRV Nano SDK and MaixCDK, and is compatible with materials that use the LicheeRV Nano, opposite the KVM software cannot be used with the LicheeRV Nano or other SG2002 products. If you would like to build an HDMI input application on LicheeRV Nano or MaixCam, please contact us for technical support.
Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the multimedia subsystem, which NanoKVM will use for video image acquisition and processing
Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the multimedia subsystem, which NanoKVM will use for video image acquisition and processing.
+ [NanoKVM-A Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf)
+ [NanoKVM-B Schematic](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf)
@@ -85,21 +91,28 @@ Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the mu
**Enhancements:**
- [ ] Refactor the HDMI module and remove the libmaixcam_lib.so dependency
- [ ] Disable SSH by default and add an enable/disable toggle in the web UI
- [ ] Disable Tailscale by default and add an enable/disable toggle in the web UI
- [ ] Add JWT configuration options to the configuration file
- [x] Refactor the HDMI module and remove the libmaixcam_lib.so dependency
- [x] Disable SSH by default and add an enable/disable toggle in the web UI
- [x] Disable Tailscale by default and add an enable/disable toggle in the web UI
- [x] Add JWT configuration options to the configuration file
- [x] Secure password storage with bcrypt
**Distribution:**
- [ ] Distribute applications via GitHub
- [ ] Implement integrity checks for online updates
- [x] Distribute applications via GitHub
- [x] Implement integrity checks for online updates
- [ ] Support offline updates
**Bug Fixes:**
- [ ] Fix the DNS issue
- [ ] Fix the CSRF vulnerability
- [x] Fix the DNS issue
- [x] Fix the CSRF vulnerability
**Features**
- [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Support Zerotier
- [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) Support WireGuard
- [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) Add Mouse Jiggler
## Where to buy
@@ -107,11 +120,7 @@ Note: Out of the 256MB memory in SG2002, 158MB is currently allocated for the mu
* [淘宝](https://item.taobao.com/item.htm?id=811206560480)
* [Preorder (anyother country that not support in Aliexpress or Taobao)](https://sipeed.com/nanokvm)
## Community
* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)
* QQ group: 703230713

View File

@@ -41,7 +41,7 @@ NanoKVM Fullは完全版で、洗練されたケースと完全なアクセサ
| --- | --- | --- | --- |
| 計算ユニット | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| 解像度 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
| ビデオエンコーディング | MJPEG, H264(開発中) | MJPEG, H264(開発中) | MJPEG, H264 |
| ビデオエンコーディング | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
| ビデオ遅延 | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| 仮想HID | ✓ | ✓ | ✓ |

View File

@@ -33,7 +33,7 @@ NanoKVM Full 为完整版配置,带精致外壳和完整配件,内置开机
| --- | --- | --- | --- |
| 计算单元 | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) |
| 分辨率 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps |
| 视频编码 | MJPEG, H264(developing) | MJPEG, H264(developing) | MJPEG, H264 |
| 视频编码 | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 |
| 视频延迟 | 90~230ms | 90~230ms | 100~230ms |
| UEFI/BIOS | ✓ | ✓ | ✓ |
| 模拟USB键鼠 | ✓ | ✓ | ✓ |

View File

@@ -85,7 +85,7 @@ func getHardware() (h Hardware) {
default:
h = HWAlpha
log.Error("Unsupported hardware version: %s", version)
log.Errorf("Unsupported hardware version: %s", version)
}
return

Binary file not shown.

Binary file not shown.

View File

@@ -5,9 +5,14 @@ type GetImagesRsp struct {
}
type MountImageReq struct {
File string `json:"file" validate:"omitempty"`
File string `json:"file" validate:"omitempty"`
Cdrom bool `json:"cdrom" validate:"omitempty"`
}
type GetMountedImageRsp struct {
File string `json:"file"`
}
type GetCdRomRsp struct {
Cdrom int64 `json:"cdrom"`
}

View File

@@ -14,4 +14,5 @@ func storageRouter(r *gin.Engine) {
api.GET("/storage/image", service.GetImages) // get image list
api.GET("/storage/image/mounted", service.GetMountedImage) // get mounted image
api.POST("/storage/image/mount", service.MountImage) // mount image
api.GET("/storage/cdrom", service.GetCdRom) // get CD-ROM flag
}

View File

@@ -15,7 +15,7 @@ const AccountFile = "/etc/kvm/pwd"
type Account struct {
Username string `json:"username"`
Password string `json:"password"`
Password string `json:"password"` // should be named HashedPassword for clarity
}
func GetAccount() (*Account, error) {
@@ -40,10 +40,10 @@ func GetAccount() (*Account, error) {
return &account, nil
}
func SetAccount(username string, password string) error {
func SetAccount(username string, hashedPassword string) error {
account, err := json.Marshal(&Account{
Username: username,
Password: password,
Password: hashedPassword,
})
if err != nil {
log.Errorf("failed to marshal account information to json: %s", err)
@@ -65,7 +65,7 @@ func SetAccount(username string, password string) error {
return nil
}
func CompareAccount(username string, password string) bool {
func CompareAccount(username string, plainPassword string) bool {
account, err := GetAccount()
if err != nil {
return false
@@ -75,16 +75,16 @@ func CompareAccount(username string, password string) bool {
return false
}
decryptedPassword, err := utils.DecodeDecrypt(password)
if err != nil || decryptedPassword == "" {
hashedPassword, err := utils.DecodeDecrypt(plainPassword)
if err != nil || hashedPassword == "" {
return false
}
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(decryptedPassword))
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(hashedPassword))
if err != nil {
// Compatible with old versions
accountDecryptedPassword, _ := utils.DecodeDecrypt(account.Password)
if accountDecryptedPassword == decryptedPassword {
accountHashedPassword, _ := utils.DecodeDecrypt(account.Password)
if accountHashedPassword == hashedPassword {
return true
}
@@ -104,10 +104,10 @@ func DelAccount() error {
}
func getDefaultAccount() *Account {
password, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
return &Account{
Username: "admin",
Password: string(password),
Password: string(hashedPassword),
}
}

View File

@@ -3,6 +3,7 @@ package auth
import (
"NanoKVM-Server/proto"
"NanoKVM-Server/utils"
"errors"
"io"
"os"
"os/exec"
@@ -70,7 +71,9 @@ func (s *Service) IsPasswordUpdated(c *gin.Context) {
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte("admin"))
rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{
IsUpdated: err == nil,
// If the hash is not valid, still assume it's not updated
// The error we want to see is password and hash not matching
IsUpdated: errors.Is(err, bcrypt.ErrMismatchedHashAndPassword),
})
}

View File

@@ -1,11 +1,12 @@
package storage
import (
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -18,9 +19,7 @@ const (
imageNone = "/dev/mmcblk0p3"
cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom"
mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
removableFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/removable"
roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro"
roDisk = "/boot/usb.disk0.ro"
)
func (s *Service) GetImages(c *gin.Context) {
@@ -61,55 +60,43 @@ func (s *Service) MountImage(c *gin.Context) {
return
}
// cdrom and ro flag
// set to 0 when unmount image
// set to 1 when mount image and the CD-ROM is enabled
if req.File == "" || req.Cdrom {
flag := "0"
if req.File != "" && req.Cdrom {
flag = "1"
}
// unmount
if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil {
log.Errorf("unmount file failed: %s", err)
rsp.ErrRsp(c, -2, "unmount image failed")
return
}
// ro flag
if err := os.WriteFile(roFlag, []byte(flag), 0o666); err != nil {
log.Errorf("set ro flag failed: %s", err)
rsp.ErrRsp(c, -2, "set ro flag failed")
return
}
// cdrom flag
if err := os.WriteFile(cdromFlag, []byte(flag), 0o666); err != nil {
log.Errorf("set cdrom flag failed: %s", err)
rsp.ErrRsp(c, -2, "set cdrom flag failed")
return
}
}
// mount
image := req.File
if image == "" {
image = imageNone
}
imageRemovable := "1"
imageRo := "0"
isImageRo, _ := isFlagExist(roDisk)
if isImageRo {
imageRo = "1"
}
imageLow := strings.ToLower(image)
imageCdrom := "0"
if strings.HasSuffix(imageLow, ".iso") {
imageRo = "1"
imageCdrom = "1"
}
// unmount
if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil {
log.Errorf("unmount file failed: %s", err)
rsp.ErrRsp(c, -2, "unmount image failed")
return
}
// removable flag
if err := os.WriteFile(removableFlag, []byte(imageRemovable), 0o666); err != nil {
log.Errorf("set removable flag failed: %s", err)
rsp.ErrRsp(c, -2, "set removable flag failed")
return
}
// ro flag
if err := os.WriteFile(roFlag, []byte(imageRo), 0o666); err != nil {
log.Errorf("set ro flag failed: %s", err)
rsp.ErrRsp(c, -2, "set ro flag failed")
return
}
// cdrom flag
if err := os.WriteFile(cdromFlag, []byte(imageCdrom), 0o666); err != nil {
log.Errorf("set cdrom flag failed: %s", err)
rsp.ErrRsp(c, -2, "set cdrom flag failed")
return
}
// mount
if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil {
log.Errorf("mount file %s failed: %s", image, err)
rsp.ErrRsp(c, -2, "mount image failed")
@@ -128,6 +115,7 @@ func (s *Service) MountImage(c *gin.Context) {
rsp.ErrRsp(c, -2, "execute command failed")
return
}
time.Sleep(100 * time.Millisecond)
}
rsp.OkRsp(c)
@@ -155,17 +143,25 @@ func (s *Service) GetMountedImage(c *gin.Context) {
rsp.OkRspWithData(c, data)
}
func isFlagExist(flag string) (bool, error) {
_, err := os.Stat(flag)
func (s *Service) GetCdRom(c *gin.Context) {
var rsp proto.Response
if err == nil {
return true, nil
content, err := os.ReadFile(cdromFlag)
if err != nil {
rsp.ErrRsp(c, -1, "read failed")
return
}
if errors.Is(err, os.ErrNotExist) {
return false, nil
flag := strings.ReplaceAll(string(content), "\n", "")
flatInt, err := strconv.ParseInt(flag, 10, 64)
if err != nil {
rsp.ErrRsp(c, -2, "parse failed")
return
}
log.Errorf("check file %s err: %s", flag, err)
return false, err
data := &proto.GetCdRomRsp{
Cdrom: flatInt,
}
rsp.OkRspWithData(c, data)
}

View File

@@ -42,7 +42,7 @@ func GetFrameRateCounter() *FrameRateCounter {
data := fmt.Sprintf("%d", counter.fps)
err := os.WriteFile("/kvmapp/kvm/now_fps", []byte(data), 0o666)
if err != nil {
log.Errorf("failed to wirte fps: %s", err)
log.Errorf("failed to write fps: %s", err)
}
}
}()

View File

@@ -93,11 +93,11 @@ func StopFrameDetect(c *gin.Context) {
go func() {
time.Sleep(20 * time.Second)
_ = os.Rename(frameDetectTmp, frameDetect)
log.Debugf("frame detect started")
log.Debug("frame detect started")
}()
rsp.OkRsp(c)
log.Debugf("frame detect stoped")
log.Debug("frame detect stopped")
}
func isFrameDetectEnabled() (bool, error) {

View File

@@ -3,7 +3,6 @@ package mjpeg
import (
"NanoKVM-Server/common"
"NanoKVM-Server/service/stream"
"fmt"
"sync"
"time"
@@ -18,7 +17,7 @@ var (
)
func Connect(c *gin.Context) {
c.Header("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=frame"))
c.Header("Content-Type", "multipart/x-mixed-replace; boundary=frame")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Pragma", "no-cache")

View File

@@ -46,7 +46,7 @@ func (s *Service) SetGpio(c *gin.Context) {
return
}
log.Debugf("gpio %s set sucessfully", device)
log.Debugf("gpio %s set successfully", device)
rsp.OkRsp(c)
}

View File

@@ -18,6 +18,7 @@ var imageVersionMap = map[string]string{
"2024-07-23-20-18-587710.img": "v1.1.0",
"2024-08-08-19-44-bef2ca.img": "v1.2.0",
"2024-11-13-09-59-9c961a.img": "v1.3.0",
"2025-02-17-16-59-3649fe.img": "v1.4.0",
}
func (s *Service) GetInfo(c *gin.Context) {

View File

@@ -30,7 +30,7 @@ func (s *Service) SetMemoryLimit(c *gin.Context) {
}
rsp.OkRsp(c)
log.Debug("set memory limit successful, enabled: %t, limit: %s", req.Enabled, req.Limit)
log.Debugf("set memory limit successful, enabled: %t, limit: %d", req.Enabled, req.Limit)
}
func (s *Service) GetMemoryLimit(c *gin.Context) {

View File

@@ -112,7 +112,7 @@ func (s *Service) RunScript(c *gin.Context) {
}
if err != nil {
log.Errorf("run script %s faile: %s", req.Name, err)
log.Errorf("run script %s failed: %s", req.Name, err.Error())
rsp.ErrRsp(c, -2, "run script failed")
return
}

View File

@@ -71,12 +71,12 @@ func (c *WsClient) Read() error {
return err
}
log.Debugf("receive message: %s", message)
log.Debugf("received message: %s", message)
var event []int
err = json.Unmarshal(message, &event)
if err != nil {
log.Debugf("receive invalid message: %s", message)
log.Debugf("received invalid message: %s", message)
continue
}

View File

View File

@@ -7,9 +7,9 @@ using namespace maix::sys;
extern kvm_sys_state_t kvm_sys_state;
extern kvm_oled_state_t kvm_oled_state;
void new_app_init(uint8_t safe_update)
void new_app_init(void)
{
// first start
// Update the necessary scripts
system("cp -f /kvmapp/system/update-nanokvm.py /etc/kvm/");
system("rm /etc/init.d/S02udisk");
system("cp /kvmapp/system/init.d/S01fs /etc/init.d/");
@@ -17,18 +17,26 @@ void new_app_init(uint8_t safe_update)
system("cp /kvmapp/system/init.d/S15kvmhwd /etc/init.d/");
system("cp /kvmapp/system/init.d/S30eth /etc/init.d/");
system("cp /kvmapp/system/init.d/S50sshd /etc/init.d/");
if(kvm_wifi_exist()) system("cp /kvmapp/system/init.d/S30wifi /etc/init.d/");
else system("rm /etc/init.d/S30wifi");
system("cp /kvmapp/system/init.d/S98tailscaled /etc/init.d/");
if(safe_update == 0){
system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/"); // /kvmapp/system/init.d/S95nanokvm还没跑完直接覆盖可能出问题
if(kvm_wifi_exist()) {
system("cp /kvmapp/system/init.d/S30wifi /etc/init.d/");
} else {
system("rm /etc/init.d/S30wifi");
}
// system("rm /etc/init.d/S04backlight");
// system("rm /etc/init.d/S05tp");
// system("rm /etc/init.d/S40bluetoothd");
// system("rm /etc/init.d/S99*");
// PCIe Patch
// system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) != 0){
system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
}
// Remove unnecessary components to speed up boot time
system("rm /etc/init.d/S04backlight");
system("rm /etc/init.d/S05tp");
system("rm /etc/init.d/S40bluetoothd");
system("rm /etc/init.d/S50ssdpd");
system("rm /etc/init.d/S99*");
// Add necessary configuration files for program execution
system("mkdir /kvmapp/kvm");
system("mkdir /etc/kvm");
system("echo 0 > /kvmapp/kvm/now_fps");
@@ -41,9 +49,8 @@ void new_app_init(uint8_t safe_update)
// rm jpg_stream & kvm_stream
system("rm -r /kvmapp/jpg_stream");
system("rm /kvmapp/kvm_system/kvm_stream");
system("rm /kvmapp/kvm_system/kvm_stream"); // Cannot delete temporarily; the old production test script uses this file to determine if the download is complete
// rm kvm_new_app
system("rm /kvmapp/kvm_new_app");
system("sync");
// system("/etc/init.d/S95nanokvm restart");
@@ -105,9 +112,15 @@ void new_app_init(uint8_t safe_update)
if(access("/root/old/kvm_new_img", F_OK) == 0){
system("rm -r /root/old");
}
system("sync");
system("killall NanoKVM-Server");
system("rm -r /tmp/server");
system("cp -r /kvmapp/server /tmp/");
system("/tmp/server/NanoKVM-Server &");
}
void update_resolv_conf(void)
void build_complete_resolv(void)
{
FILE *fp = NULL;
// fp = fopen("/boot/resolv.conf", "w+");
@@ -121,121 +134,8 @@ void update_resolv_conf(void)
system("cp -vf /boot/resolv.conf /etc/resolv.conf");
}
void init_upadte(void)
void new_img_init(void)
{
if(access("/kvmapp/kvm_new_app", F_OK) == 0){
new_app_init(0);
}
// PCIe Patch
if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) == 0){
system("cp -f /kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so /kvmapp/kvm_system/dl_lib/");
new_app_init(1);
}
// DNS Patch
system("tailscale set --accept-dns=false");
if(access("/boot/resolv.conf", F_OK) != 0){
// 不存在直接覆盖
update_resolv_conf();
} else {
// 存在则检测是否拷贝/DNS是否正确
uint8_t RW_Data[10] = {0};
FILE *fp;
fp = popen("cat /etc/resolv.conf | grep 119.29.29.29", "r");
fgets((char*)RW_Data, 2, fp);
pclose(fp);
if(RW_Data[0] != 'n') {
update_resolv_conf();
}
}
// libmaixcam_lib.so
if(access("/kvmapp/kvm_system/dl_lib/libmaixcam_lib.so", F_OK) == 0){
// exist
system("cp -f /kvmapp/kvm_system/dl_lib/libmaixcam_lib.so /kvmapp/server/dl_lib");
} else {
printf("kvm_system not exist lib!\n");
}
system("sync");
system("killall NanoKVM-Server");
system("rm -r /tmp/server");
system("cp -r /kvmapp/server /tmp/");
system("/tmp/server/NanoKVM-Server &");
}
void first_start(void)
{
if(access("/kvmapp/kvm_new_img", F_OK) == 0){
// init oled
OLED_state = oled_exist();
if(OLED_state){
printf("oled exist\r\n");
} else {
printf("oled not exist\r\n");
}
if(OLED_state){
OLED_Init();
OLED_ColorTurn(0); //0正常显示 1 反色显示
OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示
// OLED_INIT_UI();
if(kvm_hw_ver == 2){
OLED_Revolve();
}
}
uint8_t update_begin = 0;
while(1){
if(chack_net_state(ETH_ROUTE) && get_ip_addr(ETH_IP)){
OLED_Clear();
if(kvm_hw_ver != 2){
if(update_begin == 0){
update_begin = 1;
OLED_ShowString(1, 2, "Auto-updating...", 16);
OLED_ShowStringtoend(1, 4, (char*)kvm_sys_state.eth_addr, 16, 0);
system("python /kvmapp/system/update-nanokvm.py");
} else {
OLED_ShowString(1, 0, "Auto-update fail", 16);
OLED_ShowString(1, 2, "Please open the ", 16);
OLED_ShowStringtoend(1, 4, (char*)kvm_sys_state.eth_addr, 16, 0);
OLED_ShowString(1, 6, "update KVM APP ", 16);
}
} else {
// PCIe
if(update_begin == 0){
update_begin = 1;
OLED_ShowString(0, 1, "Updating...", 8);
OLED_ShowString_AlignRight(AlignRightEND_P, 2, (char*)kvm_sys_state.eth_addr, 4);
system("python /kvmapp/system/update-nanokvm.py");
} else {
OLED_ShowString(0, 0, "Update fail", 8);
OLED_ShowString(0, 1, "Please open", 8);
OLED_ShowString_AlignRight(AlignRightEND_P, 2, (char*)kvm_sys_state.eth_addr, 4);
OLED_ShowString(0, 3, "update APP" , 8);
}
}
while(1){
time::sleep_ms(1000);
if(chack_net_state(ETH_ROUTE) == 0) {
OLED_Clear();
break;
}
}
} else {
get_ip_addr(ETH_ROUTE);
if(kvm_hw_ver != 2){
OLED_ShowString(1, 2, " Please connect ", 16);
OLED_ShowString(1, 4, " to the network ", 16);
OLED_ShowString(1, 6, " to update app ", 16);
} else {
// PCIe
OLED_ShowString(0, 0, "Please link", 8);
OLED_ShowString(0, 1, "to network", 8);
OLED_ShowString(0, 2, "update APP" , 8);
}
time::sleep_ms(10);
}
}
}
build_complete_resolv();
system("rm /kvmapp/kvm_new_img");
}

View File

@@ -2,9 +2,8 @@
#define SYSTEM_INIT_H_
#include "config.h"
void new_app_init(uint8_t safe_update);
void update_resolv_conf(void);
void init_upadte(void);
void first_start(void);
void new_img_init(void);
void new_app_init(void);
#endif // SYSTEM_INIT_H_

View File

@@ -186,9 +186,17 @@ int main(int argc, char* argv[])
pthread_t display_thread;
pthread_t key_thread;
// first_start();
// Execute only when it's a new image
if(access("/kvmapp/kvm_new_img", F_OK) == 0){
new_img_init();
}
init_upadte();
// Execute only when it's a new app
if(access("/kvmapp/kvm_new_app", F_OK) == 0){
new_app_init();
}
system("sync");
OLED_state = oled_exist();
if(OLED_state){

View File

@@ -0,0 +1,6 @@
list(APPEND ADD_REQUIREMENTS vision basic peripheral)
list(APPEND ADD_INCLUDE "include")
append_srcs_dir(ADD_SRCS "src")
register_component(DYNAMIC)

View File

View File

@@ -0,0 +1,71 @@
#ifndef KVM_VISION_H_
#define KVM_VISION_H_
#include "kvm_mmf.hpp"
#include "maix_camera.hpp"
#include "maix_basic.hpp"
#include "maix_i2c.hpp"
#ifdef __cplusplus
extern "C" {
#endif
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
// #include "kvm_mmf.hpp"
#define IMG_BUFFER_FULL -3
#define IMG_VENC_ERROR -2
#define IMG_NOT_EXIST -1
#define IMG_MJPEG_TYPE 0
#define IMG_H264_TYPE_SPS 1
#define IMG_H264_TYPE_PPS 2
#define IMG_H264_TYPE_IF 3
#define IMG_H264_TYPE_PF 4
void kvmv_init(uint8_t _debug_info_en = 0);
/**********************************************************************************
* @name kvmv_read_img
* @author Sipeed BuGu
* @date 2024/10/25
* @version R1.0
* @brief Acquire the encoded image with auto init
* @param _width @input: Output image width
* @param _height @input: Output image height
* @param _type @input: Encode type
* @param _qlty @input: MJPEG: (50-100) | H264: (500-10000)
* @param _pp_kvm_data @output: Encode data
* @param _p_kvmv_data_size @output: Encode data size
* @return
-4: Modifying image resolution, please wait
-3: img buffer full
-2: VENC Error
-1: No images were acquired
0: Acquire MJPEG encoded images
1: Acquire H264 encoded images(SPS)
2: Acquire H264 encoded images(PPS)
3: Acquire H264 encoded images(I)
4: Acquire H264 encoded images(P)
**********************************************************************************/
int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_sps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int kvmv_get_pps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int free_kvmv_data(uint8_t ** _pp_kvm_data);
void free_all_kvmv_data();
void set_h264_gop(uint8_t _gop);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);
#ifdef __cplusplus
}
#endif
#endif // KVM_VISION_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
list(APPEND ADD_REQUIREMENTS basic ini)
list(APPEND ADD_INCLUDE "include")
# sophgo-middleware
set(middleware_version_str "${CONFIG_SOPHGO_MIDDLEWARE_VERSION_MAJOR}.${CONFIG_SOPHGO_MIDDLEWARE_VERSION_MINOR}.${CONFIG_SOPHGO_MIDDLEWARE_VERSION_PATCH}")
set(middleware_unzip_path "${DL_EXTRACTED_PATH}/sophgo-middleware")
set(middleware_src_path "${middleware_unzip_path}/sophgo-middleware-${middleware_version_str}")
set(source_dir "src")
append_srcs_dir(ADD_SRCS "${source_dir}")
# middleware
set(middleware_include_dir .
${middleware_src_path}/v2/component/panel/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}
${middleware_src_path}/v2/include
${middleware_src_path}/v2/include/isp/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}
${middleware_src_path}/v2/sample/common
${middleware_src_path}/v2/uapi
${middleware_src_path}/v2/3rdparty/inih
${middleware_src_path}/v2/modules/ive/include/
)
list(APPEND ADD_INCLUDE ${middleware_include_dir})
set_property(SOURCE ${middleware_include_dir} PROPERTY GENERATED 1)
append_srcs_dir(middleware_src_dir ${middleware_src_path}/v2/sample/common
${middleware_src_path}/v2/component/isp/sensor/sg200x/lontium_lt6911
)
list(APPEND ADD_SRCS ${middleware_src_dir}
"${source_dir}/kvm_mmf.cpp")
set_property(SOURCE ${middleware_src_dir} PROPERTY GENERATED 1)
set(middleware_static_lib_file "")
list(APPEND ADD_STATIC_LIB ${middleware_static_lib_file})
set_property(SOURCE ${middleware_static_lib_file} PROPERTY GENERATED 1)
if(${middleware_version_str} VERSION_EQUAL "0.0.4")
set(mmf_lib_dir ${middleware_src_path}/v2/lib)
else()
set(mmf_lib_dir ${middleware_src_path}/v2/${CONFIG_SOPHGO_MIDDLEWARE_CHIP}/lib_${CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY}_riscv64)
endif()
if(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "musl")
if(${middleware_version_str} VERSION_EQUAL "0.0.4")
set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_ive.so
${mmf_lib_dir}/libcvi_bin.so
${mmf_lib_dir}/libaaccomm2.so
${mmf_lib_dir}/libaacdec2.so
${mmf_lib_dir}/libaacenc2.so
${mmf_lib_dir}/libaacsbrdec2.so
${mmf_lib_dir}/libaacsbrenc2.so
${mmf_lib_dir}/libae.so
${mmf_lib_dir}/libaf.so
${mmf_lib_dir}/libawb.so
${mmf_lib_dir}/libcvi_audio.so
${mmf_lib_dir}/libcvi_bin_isp.so
${mmf_lib_dir}/libcvi_bin.so
${mmf_lib_dir}/libcvi_ispd2.so
${mmf_lib_dir}/libcvi_RES1.so
${mmf_lib_dir}/libcvi_ssp.so
${mmf_lib_dir}/libcvi_VoiceEngine.so
${mmf_lib_dir}/libcvi_vqe.so
${mmf_lib_dir}/libdnvqe.so
${mmf_lib_dir}/libisp_algo.so
${mmf_lib_dir}/libisp.so
${mmf_lib_dir}/libmipi_tx.so
${mmf_lib_dir}/libmisc.so
${mmf_lib_dir}/libosdc.so
${mmf_lib_dir}/libraw_dump.so
${mmf_lib_dir}/libsys.so
${mmf_lib_dir}/libvdec.so
${mmf_lib_dir}/libvenc.so
${mmf_lib_dir}/libvpu.so
${mmf_lib_dir}/libjson-c.so.5
${mmf_lib_dir}/libtinyalsa.so
${mmf_lib_dir}/3rd/libcli.so
${mmf_lib_dir}/3rd/libini.so)
else()
set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_ive.so
${mmf_lib_dir}/libcvi_bin.so
${mmf_lib_dir}/libaaccomm2.so
${mmf_lib_dir}/libaacdec2.so
${mmf_lib_dir}/libaacenc2.so
${mmf_lib_dir}/libaacsbrdec2.so
${mmf_lib_dir}/libaacsbrenc2.so
${mmf_lib_dir}/libae.so
${mmf_lib_dir}/libaf.so
${mmf_lib_dir}/libawb.so
${mmf_lib_dir}/libcipher.so
${mmf_lib_dir}/libcvi_audio.so
${mmf_lib_dir}/libcvi_bin_isp.so
${mmf_lib_dir}/libcvi_bin.so
${mmf_lib_dir}/libcvi_ispd2.so
${mmf_lib_dir}/libcvi_RES1.so
${mmf_lib_dir}/libcvi_ssp.so
${mmf_lib_dir}/libcvi_VoiceEngine.so
${mmf_lib_dir}/libcvi_vqe.so
${mmf_lib_dir}/libdnvqe.so
${mmf_lib_dir}/libisp_algo.so
${mmf_lib_dir}/libisp.so
${mmf_lib_dir}/libmipi_tx.so
${mmf_lib_dir}/libmisc.so
${mmf_lib_dir}/libosdc.so
${mmf_lib_dir}/libraw_dump.so
# ${mmf_lib_dir}/libraw_replay.so
# ${mmf_lib_dir}/libsns_full.so
# ${mmf_lib_dir}/libsns_gc4653.so
${mmf_lib_dir}/libsys.so
${mmf_lib_dir}/libvdec.so
${mmf_lib_dir}/libvenc.so
${mmf_lib_dir}/libvpu.so
${mmf_lib_dir}/libz.so
${mmf_lib_dir}/libtinyalsa.so
${mmf_lib_dir}/3rd/libcli.so
${mmf_lib_dir}/3rd/libini.so
${mmf_lib_dir}/3rd/libjson-c.so.5)
endif()
elseif(CONFIG_SOPHGO_MIDDLEWARE_C_LIBRARY STREQUAL "glibc")
set(middleware_dynamic_lib_file ${mmf_lib_dir}/libcvi_vb.so
${mmf_lib_dir}/libcvi_sys.so
${mmf_lib_dir}/libcvi_debug.so
)
else()
message(FATAL_ERROR "No sophgo middleware c library support, please check menuoconfig to select c library")
endif()
list(APPEND ADD_DYNAMIC_LIB ${middleware_dynamic_lib_file})
set_property(SOURCE ${middleware_dynamic_lib_file} PROPERTY GENERATED 1)
list(APPEND ADD_DEFINITIONS_PRIVATE -DSENSOR_LONTIUM_LT6911
-DSENSOR2_TYPE=LONTIUM_LT6911_2M_60FPS_8BIT)
register_component(DYNAMIC)
# Config enable component2 or not in Kconfig
################# Add include #################
# list(APPEND ADD_INCLUDE "include"
# )
# list(APPEND ADD_PRIVATE_INCLUDE "include_private")
###############################################
############## Add source files ###############
# list(APPEND ADD_SRCS "src/lib2.c"
# )
# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c")
# FILE(GLOB EXTRA_SRC "src/*.c")
# list(APPEND ADD_SRCS ${EXTRA_SRC})
# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS
# append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c")
# set(ADD_ASM_SRCS "src/asm.S")
# list(APPEND ADD_SRCS ${ADD_ASM_SRCS})
# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language
# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB")
###############################################
###### Add required/dependent components ######
# list(APPEND ADD_REQUIREMENTS basic ini)
###############################################
###### Add link search path for requirements/libs ######
# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib")
# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here
# set (OpenCV_DIR opencv/lib/cmake/opencv4)
# find_package(OpenCV REQUIRED)
###############################################
############ Add static libs ##################
# list(APPEND ADD_STATIC_LIB "lib/libtest.a")
###############################################
############ Add dynamic libs ##################
# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so"
# "lib/arch/v831/libmaix_cam.so"
# )
###############################################
#### Add compile option for this component ####
#### Just for this component, won't affect other
#### modules, including component that depend
#### on this component
# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1)
#### Add compile option for this component
#### and components denpend on this component
# list(APPEND ADD_DEFINITIONS -DAAAAA222=1
# -DAAAAA333=1)
###############################################
############ Add static libs ##################
#### Update parent's variables like CMAKE_C_LINK_FLAGS
# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE)
###############################################
# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib
# register_component()

View File

@@ -0,0 +1,32 @@
# sophgo middleware version major minor patch
menu "sophgo middleware version"
config SOPHGO_MIDDLEWARE_CHIP
string "sophgo chip"
default "cv181x"
help
sophgo chip. cv180x or cv181x
config SOPHGO_MIDDLEWARE_C_LIBRARY
string "sophgo c standard library"
default "musl"
help
sophgo c standard library. glibc or musl
config SOPHGO_MIDDLEWARE_VERSION_MAJOR
int "sophgo middleware package major version"
default 0
help
sophgo middleware package major version, 0 means auto select according to board
config SOPHGO_MIDDLEWARE_VERSION_MINOR
int "sophgo middleware package minor version"
default 0
help
sophgo middleware package minor version
config SOPHGO_MIDDLEWARE_VERSION_PATCH
int "sophgo middleware package patch version"
default 4
help
sophgo middleware package patch version
endmenu

View File

@@ -0,0 +1,68 @@
#ifndef __KVM_MMF_HPP__
#define __KVM_MMF_HPP__
#include "stdint.h"
typedef struct {
uint8_t *data[8];
int data_size[8];
int count;
} mmf_stream_t;
typedef struct {
uint8_t type; // 0, jpg; 1, h265; 2, h264
int w;
int h;
int fmt;
uint8_t jpg_quality; // jpeg
int gop; // h264
int intput_fps; // h264
int output_fps; // h264
int bitrate; // h264
} mmf_venc_cfg_t;
// init sys
int mmf_init(void);
int mmf_deinit(void);
int mmf_try_deinit(bool force);
bool mmf_is_init(void);
// manage vi channels(vi->vpssgroup->vpss->frame)
int mmf_get_vi_unused_channel(void);
int mmf_vi_init(void);
int mmf_vi_deinit(void);
int mmf_add_vi_channel_with_enc(int ch, int width, int height, int format);
int mmf_add_vi_channel(int ch, int width, int height, int format);
int mmf_del_vi_channel(int ch);
int mmf_del_vi_channel_all(void);
int mmf_reset_vi_channel(int ch, int width, int height, int format);
bool mmf_vi_chn_is_open(int ch);
int mmf_vi_aligned_width(int ch);
void mmf_set_vi_hmirror(int ch, bool en);
void mmf_set_vi_vflip(int ch, bool en);
void mmf_get_vi_hmirror(int ch, bool *en);
void mmf_get_vi_vflip(int ch, bool *en);
// get vi frame
int mmf_vi_frame_pop(int ch, void **data, int *len, int *width, int *height, int *format);
void mmf_vi_frame_free(int ch);
// invert format
int mmf_invert_format_to_maix(int mmf_format);
int mmf_invert_format_to_mmf(int maix_format);
// venc
int mmf_enc_jpg_init(int ch, int w, int h, int format, int quality);
int mmf_enc_jpg_deinit(int ch);
int mmf_enc_jpg_push(int ch, uint8_t *data, int w, int h, int format);
int mmf_enc_jpg_push_with_quality(int ch, uint8_t *data, int w, int h, int format, int quality);
int mmf_enc_jpg_pop(int ch, uint8_t **data, int *size);
int mmf_enc_jpg_free(int ch);
int mmf_add_venc_channel(int ch, mmf_venc_cfg_t *cfg);
int mmf_del_venc_channel(int ch);
int mmf_del_venc_channel_all();
int mmf_venc_push(int ch, uint8_t *data, int w, int h, int format);
int mmf_venc_pop(int ch, mmf_stream_t *stream);
int mmf_venc_free(int ch);
#endif // __KVM_MMF_HPP__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
# Config enable component2 or not in Kconfig
################# Add include #################
list(APPEND ADD_INCLUDE "include"
"include/base"
)
if(PLATFORM_LINUX)
list(APPEND ADD_PRIVATE_INCLUDE "port/linux")
elseif(PLATFORM_MAIXCAM)
list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam")
endif()
list(APPEND ADD_PRIVATE_INCLUDE "include_private")
###############################################
############## Add source files ###############
# list(APPEND ADD_SRCS "src/lib2.c"
# )
# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c")
# FILE(GLOB EXTRA_SRC "src/*.c")
# list(APPEND ADD_SRCS ${EXTRA_SRC})
# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS
append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
if(PLATFORM_LINUX)
append_srcs_dir(ADD_SRCS "port/linux")
elseif(PLATFORM_MAIXCAM)
append_srcs_dir(ADD_SRCS "port/maixcam")
endif()
# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c")
# set(ADD_ASM_SRCS "src/asm.S")
# list(APPEND ADD_SRCS ${ADD_ASM_SRCS})
# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language
# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB")
###############################################
###### Add required/dependent components ######
list(APPEND ADD_REQUIREMENTS basic opencv opencv_freetype websocket peripheral)
list(APPEND ADD_REQUIREMENTS omv)
list(APPEND ADD_REQUIREMENTS kvm_mmf)
###############################################
###### Add link search path for requirements/libs ######
# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib")
# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here
# set (OpenCV_DIR opencv/lib/cmake/opencv4)
# find_package(OpenCV REQUIRED)
###############################################
############ Add static libs ##################
# list(APPEND ADD_STATIC_LIB "lib/libtest.a")
###############################################
############ Add dynamic libs ##################
# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so"
# "lib/arch/v831/libmaix_cam.so"
# )
###############################################
#### Add compile option for this component ####
#### Just for this component, won't affect other
#### modules, including component that depend
#### on this component
# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1)
#### Add compile option for this component
#### and components denpend on this component
# list(APPEND ADD_DEFINITIONS -DAAAAA222=1
# -DAAAAA333=1)
###############################################
############ Add static libs ##################
#### Update parent's variables like CMAKE_C_LINK_FLAGS
# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE)
###############################################
# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib
register_component()

View File

View File

@@ -0,0 +1,109 @@
/**
* @file maix_camera_base.hpp
* @brief Maix camera SDL implementation
* @author neucrack@sipeed.com
* @license Apache 2.0 Sipeed Ltd
* @update date 2023-10-23 Create by neucrack
*/
#pragma once
#include <vector>
#include "maix_image.hpp"
#include "maix_err.hpp"
namespace maix::camera
{
class CameraBase
{
public:
/**
* @brief Construct a new Camera object
* @param device camera device name, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device
* @param width camera width, by default(value is -1) means auto detect,
* if width > max device supported width, will auto set to max device supported width
* @param height camera height, by default(value is -1) means auto detect,
* if height > max device supported height, will auto set to max device supported height
* @param format camera format, by default(value is FMT_RGB888)
* @param buff_num camera buffer number, by default(value is 3)
*/
CameraBase(const char *device = nullptr, int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3){};
/**
* @brief Judge if the given format is supported by the camera
*/
virtual bool is_support_format(image::Format format) = 0;
/**
* @brief open camera device
* @param width camera width, by default(value is -1) means auto detect,
* if width > max device supported width, will auto set to max device supported width
* @param height camera height, by default(value is -1) means auto detect,
* if height > max device supported height, will auto set to max device supported height
* @param format camera format, by default(value is FMT_RGB888)
* @param buff_num camera buffer number, by default(value is 3)
* @return error code
*/
virtual err::Err open(int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3) = 0;
/**
* @brief read a frame from camera
* @param buff buffer to store image data, if NULL, will alloc a new buffer
* @return image data
*/
virtual image::Image *read(void *buff = NULL, size_t buff_size = 0) = 0;
/**
* @brief close camera device
* @return none
*/
virtual void close() = 0;
/**
* Add a new channel and return a new Camera object, you can use close() to close this channel.
* @param width camera width, default is -1, means auto, mostly means max width of camera support
* @param height camera height, default is -1, means auto, mostly means max height of camera support
* @param format camera output format, default is RGB888
* @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
* more than one buffer will accelerate image read speed, but will cost more memory.
* @return new Camera object
*/
virtual camera::CameraBase *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int buff_num = 3) = 0;
/**
* @brief clear all buffer
* @return none
*/
virtual void clear_buff() = 0;
/**
* @brief check camera device is opened or not
* @return opened or not, bool type
*/
virtual bool is_opened() = 0;
/**
* Get camera supported channels(layers)
*/
virtual int get_ch_nums() = 0;
/**
* Get channel number of camera.
*/
virtual int get_channel() = 0;
/**
* Set/Get camera mirror
* @param en enable/disable mirror
*/
virtual int hmirror(int en) = 0;
/**
* Set/Get camera flip
* @param en enable/disable flip
*/
virtual int vflip(int en) = 0;
};
}

View File

@@ -0,0 +1,305 @@
/**
* @author neucrack@sipeed, lxowalle@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#pragma once
#include "maix_tensor.hpp"
#include "maix_log.hpp"
#include "maix_image.hpp"
#include "maix_err.hpp"
#include "maix_camera_base.hpp"
#include <stdlib.h>
#include <map>
#include <stdexcept>
#include <vector>
#include <opencv2/opencv.hpp>
/**
* @brief maix.camera module, access camera device and get image from it
* @maixpy maix.camera
*/
namespace maix::camera
{
/**
* List all supported camera devices.
* @return Returns the path to the camera device.
* @maixpy maix.camera.list_devices
*/
std::vector<std::string> list_devices();
/**
* Enable set camera registers, default is false, if set to true, will not set camera registers, you can manually set registers by write_reg API.
* @param enable enable/disable set camera registers
* @maixpy maix.camera.set_regs_enable
*/
void set_regs_enable(bool enable = true);
/**
* Camera class
* @maixpy maix.camera.Camera
*/
class Camera
{
public:
/**
* @brief Construct a new Camera object.
* Maximum resolution support 2560x1440.
* @param width camera width, default is -1, means auto, mostly means max width of camera support
* @param height camera height, default is -1, means auto, mostly means max height of camera support
* @param format camera output format, default is image.Format.FMT_RGB888
* @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device
* @param fps camera fps, default is -1, means auto, mostly means max fps of camera support
* @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
* more than one buffer will accelerate image read speed, but will cost more memory.
* @param open If true, camera will automatically call open() after creation. default is true.
* @maixpy maix.camera.Camera.__init__
* @maixcdk maix.camera.Camera.Camera
*/
Camera(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true);
/**
* @brief Construct a new Camera object.
* @attention CameraBase * parameter need to be set manually, otherwise the operation of this object will be invalid.
* @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device
* @param base basic operation objects.
* @param width camera width, default is -1, means auto, mostly means max width of camera support
* @param height camera height, default is -1, means auto, mostly means max height of camera support
* @param format camera output format, default is image.Format.FMT_RGB888
* @param fps camera fps, default is -1, means auto, mostly means max fps of camera support
* @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
* more than one buffer will accelerate image read speed, but will cost more memory.
* @param open If true, camera will automatically call open() after creation. default is true.
* @maixcdk maix.camera.Camera.Camera
*/
Camera(const char *device, CameraBase *base, int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = -1, bool open = true);
~Camera();
void restart(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true);
/**
* Get the number of channels supported by the camera.
* @return Returns the maximum number of channels.
* @maixpy maix.camera.Camera.get_ch_nums
*/
int get_ch_nums();
/**
* Open camera and run
* @param width camera width, default is -1, means auto, mostly means max width of camera support
* @param height camera height, default is -1, means auto, mostly means max height of camera support
* @param format camera output format, default same as the constructor's format argument
* @param fps camera fps, default is -1, means auto, mostly means max fps of camera support
* @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
* more than one buffer will accelerate image read speed, but will cost more memory.
* @return error code, err::ERR_NONE means success, others means failed
* @maixpy maix.camera.Camera.open
*/
err::Err open(int width = -1, int height = -1, image::Format format = image::FMT_INVALID, int fps = -1, int buff_num = -1);
/**
* Get one frame image from camera buffer, must call open method before read.
* If open method not called, will call it automatically, if open failed, will throw exception!
* So call open method before read is recommended.
* @param buff buffer to store image data, if buff is nullptr, will alloc memory automatically.
* In MaixPy, default to None, you can create a image.Image object, then pass img.data() to buff.
* @param block block read, default is true, means block util read image successfully,
* if set to false, will return nullptr if no image in buffer
* @return image::Image object, if failed, return nullptr, you should delete if manually in C++
* @maixpy maix.camera.Camera.read
*/
image::Image *read(void *buff = nullptr, size_t buff_size = 0, bool block = true);
/**
* Clear buff to ensure the next read image is the latest image
* @maixpy maix.camera.Camera.clear_buff
*/
void clear_buff();
/**
* Read some frames and drop, this is usually used avoid read not stable image when camera just opened.
* @param num number of frames to read and drop
* @maixpy maix.camera.Camera.skip_frames
*/
void skip_frames(int num);
/**
* Close camera
* @maixpy maix.camera.Camera.close
*/
void close();
/**
* Add a new channel and return a new Camera object, you can use close() to close this channel.
* @param width camera width, default is -1, means auto, mostly means max width of camera support
* @param height camera height, default is -1, means auto, mostly means max height of camera support
* @param format camera output format, default is RGB888
* @param fps camera fps, default is -1, means auto, mostly means max fps of camera support
* @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame,
* more than one buffer will accelerate image read speed, but will cost more memory.
* @param open If true, camera will automatically call open() after creation. default is true.
* @return new Camera object
* @maixpy maix.camera.Camera.add_channel
*/
camera::Camera *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = 3, bool open = true);
/**
* Check if camera is opened
* @return true if camera is opened, false if not
* @maixpy maix.camera.Camera.is_opened
*/
bool is_opened();
/**
* @brief check camera device is closed or not
* @return closed or not, bool type
* @maixpy maix.camera.Camera.is_closed
*/
bool is_closed() { return !is_opened();}
/**
* Get camera width
* @return camera width
* @maixpy maix.camera.Camera.width
*/
int width()
{
return _width;
}
/**
* Get camera height
* @return camera height
* @maixpy maix.camera.Camera.height
*/
int height()
{
return _height;
}
/**
* Get camera fps
* @return camera fps
* @maixpy maix.camera.Camera.fps
*/
int fps()
{
return _fps;
}
/**
* Get camera output format
* @return camera output format, image::Format object
* @maixpy maix.camera.Camera.format
*/
image::Format format()
{
return _format;
}
/**
* Get camera buffer number
* @return camera buffer number
* @maixpy maix.camera.Camera.buff_num
*/
int buff_num()
{
return _buff_num;
}
/**
* Set/Get camera horizontal mirror
* @return camera horizontal mirror
* @maixpy maix.camera.Camera.hmirror
*/
int hmirror(int value = -1);
/**
* Set/Get camera vertical flip
* @return camera vertical flip
* @maixpy maix.camera.Camera.vflip
*/
int vflip(int value = -1);
/**
* Get camera device path
* @return camera device path
* @maixpy maix.camera.Camera.device
*/
std::string device()
{
return _device;
}
/**
* Write camera register
* @param addr register address
* @param data register data
* @param bit_width register data bit width, default is 8
* @return error code, err::ERR_NONE means success, others means failed
* @maixpy maix.camera.Camera.write_reg
*/
err::Err write_reg(int addr, int data, int bit_width = 8)
{
return err::ERR_NONE;
}
/**
* Read camera register
* @param addr register address
* @return register data, -1 means failed
* @param bit_width register data bit width, default is 8
* @maixpy maix.camera.Camera.read_reg
*/
int read_reg(int addr, int bit_width = 8)
{
return -1;
}
/**
* Camera output color bar image for test
* @param enable enable/disable color bar
* @return error code, err::ERR_NONE means success, others means failed
* @maixpy maix.camera.Camera.show_colorbar
*/
err::Err show_colorbar(bool enable);
/**
* Get channel of camera
* @return channel number
* @maixpy maix.camera.Camera.get_channel
*/
int get_channel();
/**
* Set camera resolution
* @param width new width
* @param height new height
* @return error code, err::ERR_NONE means success, others means failed
* @maixpy maix.camera.Camera.set_resolution
*/
err::Err set_resolution(int width, int height);
private:
std::string _device;
int _ch;
int _width;
int _height;
int _fps;
int _buff_num;
image::Format _format;
image::Format _format_impl; // used by implement code and need convert to _format
int _hmirror;
int _vflip;
float _exposure;
float _gain;
bool _show_colorbar;
bool _open_set_regs;
CameraBase *_impl; // used by implement code
bool _check_format(image::Format format);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,296 @@
/**
* @author neucrack@sipeed, lxowalle@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#pragma once
#include "maix_image_def.hpp"
#include "maix_log.hpp"
namespace maix::image
{
/**
* Color class
* @maixpy maix.image.Color
*/
class Color
{
public:
/**
* Color constructor
* @param alpha alpha channel, value range: 0 ~ 1
* @maixpy maix.image.Color.__init__
*/
Color(uint8_t ch1, uint8_t ch2 = 0, uint8_t ch3 = 0, float alpha = 0, image::Format format = image::FMT_GRAYSCALE)
{
if(alpha > 1 || alpha < 0)
throw std::runtime_error("alpha value range: 0 ~ 1");
this->format = format;
switch (format)
{
case image::FMT_RGB888:
r = ch1;
g = ch2;
b = ch3;
this->alpha = 1;
break;
case image::FMT_BGR888:
b = ch1;
g = ch2;
r = ch3;
this->alpha = 1;
break;
case image::FMT_GRAYSCALE:
gray = ch1;
break;
case image::FMT_BGRA8888:
b = ch1;
g = ch2;
r = ch3;
this->alpha = alpha;
break;
case image::FMT_RGBA8888:
r = ch1;
g = ch2;
b = ch3;
this->alpha = alpha;
break;
default:
throw std::runtime_error("not support format");
break;
}
}
/**
* Color red channel
* @maixpy maix.image.Color.r
*/
uint8_t r;
/**
* Color green channel
* @maixpy maix.image.Color.g
*/
uint8_t g;
/**
* Color blue channel
* @maixpy maix.image.Color.b
*/
uint8_t b;
/**
* Color alpha channel, value from 0.0 to 1.0, float value
* @maixpy maix.image.Color.alpha
*/
float alpha;
/**
* Color gray channel
* @maixpy maix.image.Color.gray
*/
uint8_t gray;
/**
* Color format
* @maixpy maix.image.Color.format
*/
image::Format format;
/**
* Get color's hex value
* @maixpy maix.image.Color.hex
*/
uint32_t hex()
{
uint32_t hex = 0;
switch (format)
{
case image::FMT_RGB888:
hex = r | (g << 8) | (b << 16);
break;
case image::FMT_BGR888:
hex = b | (g << 8) | (r << 16);
break;
case image::FMT_GRAYSCALE:
hex = gray;
break;
case image::FMT_BGRA8888:
hex = b | (g << 8) | (r << 16) | ((uint8_t)(alpha*255) << 24);
break;
case image::FMT_RGBA8888:
hex = r | (g << 8) | (b << 16) | ((uint8_t)(alpha*255) << 24);
break;
default:
throw std::runtime_error("not support format");
break;
}
return hex;
}
/**
* Create Color object from RGB channels
* @maixpy maix.image.Color.from_rgb
*/
static image::Color from_rgb(uint8_t r, uint8_t g, uint8_t b)
{
return Color(r, g, b, 1, image::Format::FMT_RGB888);
}
/**
* Create Color object from BGR channels
* @maixpy maix.image.Color.from_bgr
*/
static image::Color from_bgr(uint8_t b, uint8_t g, uint8_t r)
{
return Color(b, g, r, 1, image::Format::FMT_BGR888);
}
/**
* Create Color object from gray channel
* @maixpy maix.image.Color.from_gray
*/
static image::Color from_gray(uint8_t gray)
{
return Color(gray);
}
/**
* Create Color object from RGBA channels
* @param alpha alpha channel, float value, value range: 0 ~ 1
* @maixpy maix.image.Color.from_rgba
*/
static image::Color from_rgba(uint8_t r, uint8_t g, uint8_t b, float alpha)
{
return Color(r, g, b, alpha, image::Format::FMT_RGBA8888);
}
/**
* Create Color object from BGRA channels
* * @param alpha alpha channel, float value, value range: 0 ~ 1
* @maixpy maix.image.Color.from_bgra
*/
static image::Color from_bgra(uint8_t b, uint8_t g, uint8_t r, float alpha)
{
return Color(b, g, r, alpha, image::Format::FMT_BGRA8888);
}
/**
* Create Color object from hex value
* @param hex hex value, e.g. 0x0000FF00, lower address if first channel
* @param format color format, @see image::Format
* @maixpy maix.image.Color.from_hex
*/
static image::Color from_hex(uint32_t hex, image::Format &format)
{
return Color(hex & 0xFF, hex & 0xFF00, hex & 0xFF0000, (hex & 0xFF000000)/255.0, format);
}
/**
* Convert Color format
* @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE.
* @maixpy maix.image.Color.to_format
*/
void to_format(const image::Format &format)
{
if(!(format == image::FMT_RGB888 || format == image::FMT_BGR888 ||
format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 ||
format == image::FMT_GRAYSCALE))
{
log::error("convert format failed, not support format %d\n", format);
return;
}
if(this->format == format)
return;
if((this->format == image::FMT_RGB888 || this->format == image::FMT_BGR888) &&
(format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888))
{
this->alpha = 1;
}
else if(this->format == image::FMT_GRAYSCALE && format != image::FMT_GRAYSCALE)
{
this->r = this->gray;
this->g = this->gray;
this->b = this->gray;
this->alpha = 1;
}
else if((this->format == image::FMT_RGBA8888 || this->format == image::FMT_BGRA8888) &&
(format == image::FMT_RGB888 || format == image::FMT_BGR888))
{
this->alpha = 0;
}
else if(this->format != image::FMT_GRAYSCALE && format == image::FMT_GRAYSCALE)
{
this->gray = (this->r + this->g + this->b) / 3;
this->r = this->gray;
this->g = this->gray;
this->b = this->gray;
this->alpha = 0;
}
this->format = format;
}
/**
* Convert color format and return a new Color object
* @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE.
* @return new Color object, you need to delete it manually in C++.
* @maixpy maix.image.Color.to_format2
*/
image::Color *to_format2(const image::Format &format)
{
image::Color *color = new image::Color(*this);
color->to_format(format);
return color;
}
};
/**
* Predefined color white
* @maixpy maix.image.COLOR_WHITE
*/
const image::Color COLOR_WHITE = image::Color::from_rgb(255, 255, 255);
/**
* Predefined color black
* @maixpy maix.image.COLOR_BLACK
*/
const image::Color COLOR_BLACK = image::Color::from_rgb(0, 0, 0);
/**
* Predefined color red
* @maixpy maix.image.COLOR_RED
*/
const image::Color COLOR_RED = image::Color::from_rgb(255, 0, 0);
/**
* Predefined color green
* @maixpy maix.image.COLOR_GREEN
*/
const image::Color COLOR_GREEN = image::Color::from_rgb(0, 255, 0);
/**
* Predefined color blue
* @maixpy maix.image.COLOR_BLUE
*/
const image::Color COLOR_BLUE = image::Color::from_rgb(0, 0, 255);
/**
* Predefined color yellow
* @maixpy maix.image.COLOR_YELLOW
*/
const image::Color COLOR_YELLOW = image::Color::from_rgb(255, 255, 0);
/**
* Predefined color purple
* @maixpy maix.image.COLOR_PURPLE
*/
const image::Color COLOR_PURPLE = image::Color::from_rgb(143, 0, 255);
/**
* Predefined color orange
* @maixpy maix.image.COLOR_ORANGE
*/
const image::Color COLOR_ORANGE = image::Color::from_rgb(255, 127, 0);
/**
* Predefined color gray
* @maixpy maix.image.COLOR_GRAY
*/
const image::Color COLOR_GRAY = image::Color::from_rgb(127, 127, 127);
}

View File

@@ -0,0 +1,270 @@
/**
* @author neucrack@sipeed, lxowalle@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#pragma once
#include <vector>
#include <string>
#include <map>
#include <stdint.h>
#include <stdexcept>
namespace maix::image
{
/**
* Image formats
* @attention for developers, update this enum will also need to update the fmt_size in maix_image.cpp
* @maixpy maix.image.Format
*/
enum Format
{
FMT_RGB888 = 0, // RGBRGB...RGB, R at the lowest address
FMT_BGR888, // BGRBGR...BGR, B at the lowest address
FMT_RGBA8888, // RGBARGBA...RGBA, R at the lowest address
FMT_BGRA8888, // BGRABGRA...BGRA, B at the lowest address
FMT_RGB565,
FMT_BGR565,
FMT_YUV422SP, // YYY...UVUVUV...UVUV
FMT_YUV422P, // YYY...UUU...VVV
FMT_YVU420SP, // YYY...VUVUVU...VUVU, NV21
FMT_YUV420SP, // YYY...UVUVUV...UVUV, NV12
FMT_YVU420P, // YYY...VVV...UUU
FMT_YUV420P, // YYY...UUU...VVV
FMT_GRAYSCALE,
FMT_UNCOMPRESSED_MAX,
// compressed format below, not compressed should define upper
FMT_COMPRESSED_MIN,
FMT_JPEG,
FMT_PNG,
FMT_COMPRESSED_MAX,
FMT_INVALID = 0xFF // format not valid
};
// enum Format
// {
// FMT_RGB888 = 0, // RGBRGB...RGB, R at the lowest address
// FMT_BGR888 = 1, // BGRBGR...BGR, B at the lowest address
// FMT_RGBA8888 = 2, // RGBARGBA...RGBA, R at the lowest address
// FMT_BGRA8888 = 3, // BGRABGRA...BGRA, B at the lowest address
// FMT_RGB565 = 4,
// FMT_BGR565 = 5,
// FMT_YUV422SP = 6, // YYY...UVUVUV...UVUV
// FMT_YUV422P = 7, // YYY...UUU...VVV
// FMT_YVU420SP = 8, // YYY...VUVUVU...VUVU, NV21
// FMT_YUV420SP = 9, // YYY...UVUVUV...UVUV, NV12
// FMT_YVU420P = 10, // YYY...VVV...UUU
// FMT_YUV420P = 11, // YYY...UUU...VVV
// FMT_GRAYSCALE = 12, // YYY...
// FMT_UNCOMPRESSED_MAX,
// // compressed format below, not compressed should define upper
// FMT_COMPRESSED_MIN = 200,
// FMT_JPEG = 201,
// FMT_PNG = 202,
// FMT_COMPRESSED_MAX,
// FMT_INVALID = 0xFF // format not valid
// };
/**
* Image format size in bytes
* @attention It's a copy of this variable in MaixPy,
* so change it in C++ (e.g. update var in hello function) will not take effect the var inMaixPy.
* So we add const for this var to avoid this mistake.
* @maixpy maix.image.fmt_size
*/
const std::vector<float> fmt_size = {
3,
3,
4,
4,
2,
2,
2,
2,
1.5,
1.5,
1.5,
1.5,
1,
0,
0,
0,
0,
0
};
/**
* Image format string
* @maixpy maix.image.fmt_names
*/
const std::vector<std::string> fmt_names = {
"RGB888",
"BGR888",
"RGBA8888",
"BGRA8888",
"RGB565",
"BGR565",
"YUV422SP",
"YUV422P",
"YVU420SP",
"YUV420SP",
"YVU420P"
"YUV420P",
"GRAYSCALE",
"MAX"};
/**
* Image size type
* @maixpy maix.image.Size
*/
class Size
{
public:
/**
* Construct a new Size object
* @param width image width
* @param height image height
* @maixpy maix.image.Size.__init__
*/
Size(int width = 0, int height = 0)
{
this->_width = width;
this->_height = height;
}
/**
* width of size
* @param width set new width, if not set, only return current width
* @maixpy maix.image.Size.width
*/
int width(int width = -1)
{
if(width != -1)
{
this->_width = width;
}
return this->_width;
}
/**
* height of size
* @param height set new height, if not set, only return current height
* @maixpy maix.image.Size.height
*/
int height(int height = -1)
{
if(height != -1)
{
this->_height = height;
}
return this->_height;
}
/**
* Subscript operator
* @param index 0 for width, 1 for height
* @return int& width or height
* @maixpy maix.image.Size.__getitem__
* @maixcdk maix.image.Size.[]
*/
int &operator[](int index)
{
if (index == 0)
return _width;
else if (index == 1)
return _height;
else
throw std::out_of_range("Size index out of range");
}
/**
* to string
* @maixpy maix.image.Size.__str__
*/
std::string __str__()
{
return "Size(" + std::to_string(_width) + "x" + std::to_string(_height) + ")";
}
private:
int _width;
int _height;
};
/**
* Object fit method
* @maixpy maix.image.Fit
*/
enum Fit
{
FIT_NONE = -1, // no object fit, keep original
FIT_FILL = 0, // width to new width, height to new height, may be stretch
FIT_CONTAIN, // keep aspect ratio, fill blank area with black color
FIT_COVER, // keep aspect ratio, crop image to fit new size
FIT_MAX
};
/**
* Resize method
* @maixpy maix.image.ResizeMethod
*/
enum ResizeMethod
{
NEAREST = 0,
BILINEAR,
BICUBIC,
AREA,
LANCZOS,
HAMMING,
RESIZE_METHOD_MAX
};
/**
* Family of apriltag
* @maixpy maix.image.ApriltagFamilies
*/
enum ApriltagFamilies
{
TAG16H5 = 1,
TAG25H7 = 2,
TAG25H9 = 4,
TAG36H10 = 8,
TAG36H11 = 16,
ARTOOLKIT = 32
};
/**
* Template match method
* @maixpy maix.image.TemplateMatch
*/
enum TemplateMatch
{
SEARCH_EX, // Exhaustive search
SEARCH_DS, // Diamond search
};
/**
* CornerDetector class
* @maixpy maix.image.CornerDetector
*/
enum CornerDetector
{
CORNER_FAST,
CORNER_AGAST
};
/**
* EdgeDetector class
* @maixpy maix.image.EdgeDetector
*/
enum EdgeDetector
{
EDGE_CANNY,
EDGE_SIMPLE,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
#include "maix_image.hpp"
#include "maix_display.hpp"
#include "maix_camera.hpp"
#include "maix_video.hpp"

View File

@@ -0,0 +1,17 @@
#pragma once
#include "maix_image.hpp"
#include "omv.hpp"
namespace maix::image
{
/**
* Convert image to openmv image format
* @param image image
* @param imlib_image openmv image
* @return
*/
extern void convert_to_imlib_image(image::Image *image, image_t *imlib_image);
extern void _convert_to_lab_thresholds(std::vector<std::vector<int>> &in, list_t *out);
}

View File

@@ -0,0 +1,317 @@
/**
* @author lxowalle@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#pragma once
#include <stdint.h>
#include "maix_err.hpp"
#include "maix_basic.hpp"
#include "maix_log.hpp"
#include "maix_image.hpp"
#include "maix_time.hpp"
#include "maix_camera_base.hpp"
#include "kvm_mmf.hpp"
#include <signal.h>
static void try_deinit_mmf()
{
static uint8_t is_called = 0;
if (!is_called) {
mmf_try_deinit(true);
is_called = 1;
}
}
static void signal_handle(int signal)
{
const char *signal_msg = NULL;
switch (signal) {
case SIGILL: signal_msg = "SIGILL"; break;
case SIGTRAP: signal_msg = "SIGTRAP"; break;
case SIGABRT: signal_msg = "SIGABRT"; break;
case SIGBUS: signal_msg = "SIGBUS"; break;
case SIGFPE: signal_msg = "SIGFPE"; break;
case SIGKILL: signal_msg = "SIGKILL"; break;
case SIGSEGV: signal_msg = "SIGSEGV"; break;
default: signal_msg = "UNKNOWN"; break;
}
maix::log::error("Trigger signal, code:%s(%d)!\r\n", signal_msg, signal);
try_deinit_mmf();
exit(1);
}
// FIXME: move this function to port/maix_vision_maixcam.cpp ?
static __attribute__((constructor)) void maix_vision_register_signal(void)
{
signal(SIGILL, signal_handle);
signal(SIGTRAP, signal_handle);
signal(SIGABRT, signal_handle);
signal(SIGBUS, signal_handle);
signal(SIGFPE, signal_handle);
signal(SIGKILL, signal_handle);
signal(SIGSEGV, signal_handle);
maix::util::register_exit_function(try_deinit_mmf);
}
namespace maix::camera
{
class CameraCviMmf final : public CameraBase
{
public:
CameraCviMmf(const std::string device, int width, int height, image::Format format, int buff_num)
{
this->device = device;
this->format = format;
this->width = width;
this->height = height;
this->buffer_num = buff_num;
this->ch = -1;
if (0 != mmf_init()) {
err::check_raise(err::ERR_RUNTIME, "mmf init failed");
}
if (0 != mmf_vi_init()) {
err::check_raise(err::ERR_RUNTIME, "mmf vi init failed");
}
}
CameraCviMmf(const std::string device, int ch, int width, int height, image::Format format, int buff_num)
{
this->device = device;
this->format = format;
this->width = width;
this->height = height;
this->buffer_num = buff_num;
this->ch = ch;
if (0 != mmf_init()) {
err::check_raise(err::ERR_RUNTIME, "mmf init failed");
}
if (0 != mmf_vi_init()) {
err::check_raise(err::ERR_RUNTIME, "mmf vi init failed");
}
}
~CameraCviMmf()
{
mmf_del_vi_channel(this->ch);
if (0 != mmf_vi_deinit()) {
err::check_raise(err::ERR_RUNTIME, "mmf vi init failed");
}
mmf_try_deinit(true);
}
err::Err open(int width, int height, image::Format format, int buff_num)
{
if (format == image::FMT_GRAYSCALE) {
format = image::FMT_YVU420SP;
}
int ch = mmf_get_vi_unused_channel();
if (ch < 0) {
log::error("camera open: mmf get vi channel failed");
return err::ERR_RUNTIME;
}
if (0 != mmf_add_vi_channel(ch, width, height, mmf_invert_format_to_mmf(format))) {
log::error("camera open: mmf add vi channel failed");
return err::ERR_RUNTIME;
}
this->ch = ch;
this->width = (width == -1) ? this->width : width;
this->height = (height == -1) ? this->height : height;
this->align_width = mmf_vi_aligned_width(this->ch);
this->align_need = (this->width % this->align_width == 0) ? false : true; // Width need align only
return err::ERR_NONE;
} // open
bool is_support_format(image::Format format)
{
if(format == image::Format::FMT_RGB888 || format == image::Format::FMT_BGR888
|| format == image::Format::FMT_YVU420SP|| format == image::Format::FMT_GRAYSCALE)
return true;
return false;
}
void close()
{
if (mmf_vi_chn_is_open(this->ch) == true) {
if (0 != mmf_del_vi_channel(this->ch)) {
log::error("mmf del vi channel failed");
}
}
} // close
// read
image::Image *read(void *buff = NULL, size_t buff_size = 0)
{
// printf("Func: maix_image.cpp read\r\n");
image::Image *img = NULL;
void *buffer = NULL;
int buffer_len = 0, width = 0, height = 0, format = 0;
if (0 == mmf_vi_frame_pop(this->ch, &buffer, &buffer_len, &width, &height, &format)) {
if (buffer == NULL) {
mmf_vi_frame_free(this->ch);
printf("mmf_vi_frame_free error\r\n");
return NULL;
}
if(buff)
{
if(buff_size < (size_t)buffer_len)
{
log::error("camera read: buff size not enough, need %d, but %d", buffer_len, buff_size);
mmf_vi_frame_free(this->ch);
return NULL;
}
img = new image::Image(width, height, this->format, (uint8_t*)buff, buff_size, false);
}
else
{
img = new image::Image(this->width, this->height, this->format);
}
void *image_data = img->data();
switch (img->format()) {
case image::Format::FMT_GRAYSCALE:
if (this->align_need) {
for (int h = 0; h < this->height; h ++) {
memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width);
}
} else {
memcpy(image_data, buffer, this->width * this->height);
}
break;
case image::Format::FMT_BGR888: // fall through
case image::Format::FMT_RGB888:
if (this->align_need) {
for (int h = 0; h < this->height; h++) {
memcpy((uint8_t *)image_data + h * this->width * 3, (uint8_t *)buffer + h * width * 3, this->width * 3);
}
} else {
memcpy(image_data, buffer, this->width * this->height * 3);
}
break;
case image::Format::FMT_YVU420SP:
if (this->align_need) {
for (int h = 0; h < this->height * 3 / 2; h ++) {
memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width);
}
} else {
memcpy(image_data, buffer, this->width * this->height * 3 / 2);
}
break;
default:
printf("unknown format\n");
delete img;
mmf_vi_frame_free(this->ch);
printf("switch (img->format()\r\n");
return NULL;
}
mmf_vi_frame_free(this->ch);
// printf("mmf_vi_frame_free\r\n");
return img;
}
return img;
} // read
camera::CameraCviMmf *add_channel(int width, int height, image::Format format, int buff_num)
{
int new_channel = mmf_get_vi_unused_channel();
if (new_channel < 0) {
log::error("Support not more channel!\r\n");
return NULL;
}
return new camera::CameraCviMmf(this->device, new_channel, width, height, format, buff_num);
}
void clear_buff()
{
}
bool is_opened() {
return mmf_vi_chn_is_open(this->ch);
}
int get_ch_nums() {
return 1;
}
int get_channel() {
return this->ch;
}
int hmirror(int en)
{
bool out;
if (en == -1) {
mmf_get_vi_hmirror(this->ch, &out);
} else {
bool need_open = false;
if (this->is_opened()) {
this->close();
need_open = true;
}
mmf_set_vi_hmirror(this->ch, en);
if (need_open) {
err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed");
}
out = en;
}
return out;
}
int vflip(int en)
{
bool out;
if (en == -1) {
mmf_get_vi_vflip(this->ch, &out);
} else {
bool need_open = false;
if (this->is_opened()) {
this->close();
need_open = true;
}
mmf_set_vi_vflip(this->ch, en);
if (need_open) {
err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed");
}
out = en;
}
return out;
}
private:
std::string device;
image::Format format;
int fd;
int ch;
uint32_t raw_format;
std::vector<void*> buffers;
std::vector<int> buffers_len;
int buffer_num;
int queue_id; // user directly used buffer id
int width;
int height;
void *buff;
bool buff_alloc;
int align_width;
bool align_need;
};
} // namespace maix::camera

View File

@@ -0,0 +1,425 @@
/**
* @author neucrack@sipeed, lxowalle@sipeed
* @copyright Sipeed Ltd 2023-
* @license Apache 2.0
* @update 2023.9.8: Add framework, create this file.
*/
#include "maix_camera.hpp"
#include <dirent.h>
#ifdef PLATFORM_LINUX
#include "maix_camera_v4l2.hpp"
#endif
#ifdef PLATFORM_MAIXCAM
#include "maix_camera_mmf.hpp"
#endif
namespace maix::camera
{
static bool set_regs_flag = false;
std::vector<std::string> list_devices()
{
// find to /dev/video*
std::vector<std::string> devices;
std::string path = "/dev";
DIR *dir = opendir(path.c_str());
if (dir == NULL)
{
return devices;
}
struct dirent *ptr;
while ((ptr = readdir(dir)) != NULL)
{
if (ptr->d_type == DT_CHR)
{
std::string name = ptr->d_name;
if (name.find("video") != std::string::npos)
{
devices.push_back( path + "/" + name );
}
}
}
closedir(dir);
// sort devices with name
std::sort(devices.begin(), devices.end());
// print devices
for (size_t i = 0; i < devices.size(); i++)
{
log::debug("find device: %s\n", devices[i].c_str());
}
return devices;
}
void set_regs_enable(bool enable) {
set_regs_flag = enable;
}
err::Err Camera::show_colorbar(bool enable)
{
// only set variable now
// should control camera to show colorbar
_show_colorbar = enable;
return err::ERR_NONE;
}
static void generate_colorbar(image::Image &img)
{
int width = img.width();
int height = img.height();
int step = width / 8;
int color_step = 255 / 7;
int color = 0;
uint8_t colors[8][3] = {
{255, 255, 255},
{255, 0, 0},
{255, 127, 0},
{255, 255, 0},
{0, 255, 0},
{0, 0, 255},
{143, 0, 255},
{0, 0, 0},
};
for (int i = 0; i < 8; i++)
{
image::Color _color(colors[i][0], colors[i][1], colors[i][2], 0, image::FMT_RGB888);
img.draw_rect(i * step, 0, step, height, _color, -1);
color += color_step;
}
}
#ifdef PLATFORM_LINUX
static char * _get_device(const char *device)
{
if (device)
{
return (char *)device;
}
else
{
std::vector<std::string> devices = list_devices();
err::check_bool_raise(devices.size() > 0, "No camera device");
return (char *)devices[0].c_str();
}
}
#endif
Camera::Camera(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open)
{
err::Err e;
err::check_bool_raise(_check_format(format), "Format not support");
if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) {
log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n");
}
_width = (width == -1) ? 640 : width;
_height = (height == -1) ? 480 : height;
_format = format;
_fps = (fps == -1) ? 30 : fps;
_buff_num = buff_num;
_show_colorbar = false;
_open_set_regs = set_regs_flag;
_impl = NULL;
#ifdef PLATFORM_LINUX
_device = _get_device(device);
_impl = new CameraV4L2(_device, _width, _height, _format, _buff_num);
#endif
#ifdef PLATFORM_MAIXCAM
_device = "";
_impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num);
#endif
if (open) {
e = this->open(_width, _height, _format, _buff_num);
// err::check_raise(e, "camera open failed");
}
}
Camera::Camera(const char *device, CameraBase *base, int width, int height, image::Format format, int fps, int buff_num, bool open)
{
err::Err e;
err::check_bool_raise(_check_format(format), "Format not support");
_width = (width == -1) ? 640 : width;
_height = (height == -1) ? 480 : height;
_format = format;
_fps = (fps == -1) ? 30 : fps;
_buff_num = buff_num;
_show_colorbar = false;
_open_set_regs = set_regs_flag;
_impl = base;
if (open) {
e = this->open(_width, _height, _format, _buff_num);
err::check_raise(e, "camera open failed");
}
}
Camera::~Camera()
{
if (this->is_opened()) {
this->close();
}
#ifdef PLATFORM_LINUX
delete (CameraV4L2*)_impl;
#endif
#ifdef PLATFORM_MAIXCAM
delete (CameraCviMmf*)_impl;
#endif
}
void Camera::restart(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open)
{
if (this->is_opened()) {
this->close();
}
#ifdef PLATFORM_LINUX
delete (CameraV4L2*)_impl;
#endif
#ifdef PLATFORM_MAIXCAM
delete (CameraCviMmf*)_impl;
#endif
err::Err e;
err::check_bool_raise(_check_format(format), "Format not support");
if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) {
log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n");
}
_width = (width == -1) ? 640 : width;
_height = (height == -1) ? 480 : height;
_format = format;
_fps = (fps == -1) ? 30 : fps;
_buff_num = buff_num;
_show_colorbar = false;
_open_set_regs = set_regs_flag;
_impl = NULL;
#ifdef PLATFORM_LINUX
_device = _get_device(device);
_impl = new CameraV4L2(_device, _width, _height, _format, _buff_num);
#endif
#ifdef PLATFORM_MAIXCAM
_device = "";
_impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num);
#endif
if (open) {
e = this->open(_width, _height, _format, _buff_num);
// err::check_raise(e, "camera open failed");
}
}
int Camera::get_ch_nums()
{
return 2;
}
int Camera::get_channel()
{
if (_impl == NULL)
return err::ERR_NOT_INIT;
if (!this->is_opened()) {
return err::ERR_NOT_OPEN;
}
return _impl->get_channel();
}
bool Camera::_check_format(image::Format format) {
if (format == image::FMT_RGB888 || format == image::FMT_BGR888
|| format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888
|| format == image::FMT_YVU420SP || format == image::FMT_GRAYSCALE) {
return true;
} else {
return false;
}
}
err::Err Camera::open(int width, int height, image::Format format, int fps, int buff_num)
{
if (_impl == NULL)
return err::Err::ERR_RUNTIME;
int width_tmp = (width == -1) ? _width : width;
int height_tmp = (height == -1) ? _height : height;
image::Format format_tmp = (format == image::FMT_INVALID) ? _format : format;
int fps_tmp = (fps == -1) ? 30 : fps;
int buff_num_tmp =( buff_num == -1) ? _buff_num : buff_num;
err::check_bool_raise(_check_format(format_tmp), "Format not support");
if (this->is_opened()) {
if (width == width_tmp && height == height_tmp && format == format_tmp && fps == fps_tmp && buff_num == buff_num_tmp) {
return err::ERR_NONE;
}
this->close(); // Get new param, close and reopen
}
_width = width_tmp;
_height = height_tmp;
_fps = fps_tmp;
_buff_num = buff_num_tmp;
_format = format_tmp;
_format_impl = _format;
if(!_impl->is_support_format(_format))
{
if(_impl->is_support_format(image::FMT_RGB888))
_format_impl = image::FMT_RGB888;
else if(_impl->is_support_format(image::FMT_BGR888))
_format_impl = image::FMT_BGR888;
else if(_impl->is_support_format(image::FMT_YVU420SP))
_format_impl = image::FMT_YVU420SP;
else if(_impl->is_support_format(image::FMT_YUV420SP))
_format_impl = image::FMT_YUV420SP;
else if(_impl->is_support_format(image::FMT_RGBA8888))
_format_impl = image::FMT_RGBA8888;
else if(_impl->is_support_format(image::FMT_BGRA8888))
_format_impl = image::FMT_BGRA8888;
else if(_impl->is_support_format(image::FMT_GRAYSCALE))
_format_impl = image::FMT_GRAYSCALE;
else
return err::ERR_ARGS;
}
return _impl->open(_width, _height, _format_impl, _buff_num);;
}
void Camera::close()
{
if (this->is_closed())
return;
_impl->close();
}
camera::Camera *Camera::add_channel(int width, int height, image::Format format, int fps, int buff_num, bool open)
{
err::check_bool_raise(_check_format(format), "Format not support");
int width_tmp = (width == -1) ? _width : width;
int height_tmp = (height == -1) ? _height : height;
image::Format format_tmp = (format == image::Format::FMT_INVALID) ? _format : format;
int fps_tmp = (fps == -1) ? _fps : fps;
int buff_num_tmp = buff_num == -1 ? _buff_num : buff_num;
Camera *cam = NULL;
if (_impl) {
CameraBase *cam_base = _impl->add_channel(width_tmp, height_tmp, format_tmp, buff_num);
err::check_bool_raise(cam_base, "Unable to add a new channel. Please check the maximum number of supported channels.");
cam = new Camera(_device.c_str(), cam_base, width_tmp, height_tmp, format_tmp, fps_tmp, buff_num_tmp, open);
}
return cam;
}
bool Camera::is_opened()
{
if (_impl == NULL)
return false;
return _impl->is_opened();
}
image::Image *Camera::read(void *buff, size_t buff_size, bool block)
{
if (!this->is_opened()) {
// err::Err e = open(_width, _height, _format, _buff_num);
// err::check_raise(e, "open camera failed");
return NULL;
}
if (_show_colorbar) {
image::Image *img = new image::Image(_width, _height);
generate_colorbar(*img);
err::check_null_raise(img, "camera read failed");
return img;
} else {
// it's better all done by impl to faster read, but if impl not support, we have to convert it
if(_format_impl == _format)
{
image::Image *img = _impl->read(buff, buff_size);
// err::check_null_raise(img, "camera read failed");
return img;
}
else
{
image::Image *img = _impl->read();
image::Image *img2 = img->to_format(_format, buff, buff_size);
delete img;
// err::check_null_raise(img2, "camera read failed");
return img2;
}
}
}
void Camera::clear_buff()
{
if (_impl == NULL)
return;
_impl->clear_buff();
}
void Camera::skip_frames(int num)
{
if (_impl == NULL)
return;
for(int i = 0; i < num; i++)
{
image::Image *img = _impl->read();
delete img;
}
}
err::Err Camera::set_resolution(int width, int height)
{
err::Err e;
if (_impl == NULL)
return err::ERR_NOT_INIT;
if (this->is_opened()) {
this->close();
}
_width = width;
_height = height;
e = this->open(_width, _height, _format, _buff_num);
err::check_raise(e, "camera open failed");
return err::ERR_NONE;
}
int Camera::hmirror(int value) {
if (_impl == NULL)
return err::ERR_NOT_INIT;
if (!this->is_opened()) {
return err::ERR_NOT_OPEN;
}
return _impl->hmirror(value);
}
int Camera::vflip(int value) {
if (_impl == NULL)
return err::ERR_NOT_INIT;
if (!this->is_opened()) {
return err::ERR_NOT_OPEN;
}
return _impl->vflip(value);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
############### Add include ###################
list(APPEND ADD_INCLUDE "include"
)
list(APPEND ADD_PRIVATE_INCLUDE "")
###############################################
############ Add source files #################
# list(APPEND ADD_SRCS "src/main.c"
# "src/test.c"
# )
append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
# list(REMOVE_ITEM COMPONENT_SRCS "src/test2.c")
# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c")
# FILE(GLOB EXTRA_SRC "src/*.c")
# list(APPEND ADD_SRCS ${EXTRA_SRC})
# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS
# append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS
# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c")
# set(ADD_ASM_SRCS "src/asm.S")
# list(APPEND ADD_SRCS ${ADD_ASM_SRCS})
# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language
# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB")
###############################################
###### Add required/dependent components ######
list(APPEND ADD_REQUIREMENTS basic peripheral kvm)
###############################################
###### Add link search path for requirements/libs ######
# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib")
# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here
# set (OpenCV_DIR opencv/lib/cmake/opencv4)
# find_package(OpenCV REQUIRED)
###############################################
############ Add static libs ##################
# list(APPEND ADD_STATIC_LIB "lib/libtest.a")
###############################################
#### Add compile option for this component ####
#### Just for this component, won't affect other
#### modules, including component that depend
#### on this component
# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1)
#### Add compile option for this component
#### and components denpend on this component
# list(APPEND ADD_DEFINITIONS -DAAAAA222=1
# -DAAAAA333=1)
###############################################
############ Add static libs ##################
#### Update parent's variables like CMAKE_C_LINK_FLAGS
# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE)
###############################################
######### Add files need to download #########
# list(APPEND ADD_FILE_DOWNLOADS "{
# 'url': 'https://*****/abcde.tar.xz',
# 'urls': [], # backup urls, if url failed, will try urls
# 'sites': [], # download site, user can manually download file and put it into dl_path
# 'sha256sum': '',
# 'filename': 'abcde.tar.xz',
# 'path': 'toolchains/xxxxx',
# 'check_files': []
# }"
# )
#
# then extracted file in ${DL_EXTRACTED_PATH}/toolchains/xxxxx,
# you can directly use then, for example use it in add_custom_command
##############################################
# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib
register_component()

View File

View File

@@ -0,0 +1,79 @@
#include "kvm_vision.h"
#include "maix_basic.hpp"
using namespace maix;
using namespace maix::sys;
// #define NOT_GET_IMG
int main(int argc, char* argv[])
{
uint64_t __attribute__((unused)) start_time;
// Catch SIGINT signal(e.g. Ctrl + C), and set exit flag to true.
signal(SIGINT, [](int sig){ app::set_exit_flag(true);
log::info("========================\n");
});
kvmv_hdmi_control(0);
kvmv_hdmi_control(1);
kvmv_init(0);
set_h264_gop(30);
uint16_t get_fream_count = 0;
while(!app::need_exit()){
#ifdef NOT_GET_IMG
printf("NOT_GET_IMG ...\n");
time::sleep_ms(1000);
#else
uint8_t* p_kvmv_img_data;
uint32_t kvmv_img_data_size;
int ret;
printf("KVM-Vison Get Fream ...\n");
start_time = time::time_ms();
if(get_fream_count < 1){
get_fream_count ++;
ret = kvmv_read_img(1920, 1080, 1, 3000, &p_kvmv_img_data, &kvmv_img_data_size);
} else if(get_fream_count >= 1 && get_fream_count < 2){
get_fream_count ++;
ret = kvmv_read_img(0, 0, 0, 60, &p_kvmv_img_data, &kvmv_img_data_size);
} else {
get_fream_count = 0;
}
// printf("kvmv_read_img(): %d \r\n", (int)(time::time_ms() - start_time));
// ret = kvmv_read_img(1920, 1080, 1, 3000, &p_kvmv_img_data, &kvmv_img_data_size);
// ret = kvmv_read_img(1920, 1080, 0, 60, &p_kvmv_img_data, &kvmv_img_data_size);
printf("kvmv_read_img ret = %d\n", ret);
if(ret == IMG_H264_TYPE_IF){
uint8_t* p_kvmv_sps_data;
uint8_t* p_kvmv_pps_data;
uint32_t kvmv_sps_data_size;
uint32_t kvmv_pps_data_size;
ret = kvmv_get_sps_frame(&p_kvmv_sps_data, &kvmv_sps_data_size);
if(ret == IMG_H264_TYPE_SPS){
printf("Get SPS data size = %d\n", kvmv_sps_data_size);
} else {
printf("Get SPS data error!\n");
}
ret = kvmv_get_pps_frame(&p_kvmv_pps_data, &kvmv_pps_data_size);
if(ret == IMG_H264_TYPE_PPS){
printf("Get PPS data size = %d\n", kvmv_pps_data_size);
} else {
printf("Get PPS data error!\n");
}
}
// send...
// if(ret >= 0){
free_kvmv_data(&p_kvmv_img_data);
// }
#endif
}
kvmv_deinit();
}

View File

@@ -11,9 +11,15 @@ export function getMountedImage() {
}
// mount/unmount image
export function mountImage(file?: string) {
export function mountImage(file?: string, cdrom?: boolean) {
const data = {
file: file ? file : ''
file: file ? file : '',
cdrom: cdrom
};
return http.post('/api/storage/image/mount', data);
}
// get CD-ROM flag
export function getCdRom() {
return http.get('/api/storage/cdrom');
}

View File

@@ -1,4 +1,5 @@
import { Button } from 'antd';
import { t } from 'i18next';
export const MainError = () => {
return (
@@ -7,10 +8,10 @@ export const MainError = () => {
role="alert"
>
<h2 className="text-lg font-semibold text-red-500">
Sorry, there seems to be some problems :(
{t('error.title')}
</h2>
<Button type="primary" danger onClick={() => window.location.assign(window.location.origin)}>
Refresh
{t('error.refresh')}
</Button>
</div>
);

View File

@@ -17,7 +17,8 @@ const languages = [
{ key: 'ja', name: '日本語' },
{ key: 'cz', name: 'Česky' },
{ key: 'uk', name: 'Українська' },
{ key: 'nb', name: 'Norsk, bokmål' }
{ key: 'nb', name: 'Norsk, bokmål' },
{ key: 'th', name: 'ภาษาไทย' }
];
languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }));

View File

@@ -28,7 +28,7 @@ const en = {
loginButtonText: 'Login',
tips: {
reset1:
'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.',
'To reset the passwords, press and hold the BOOT button on the NanoKVM for 10 seconds.',
reset2: 'For detailed steps, please consult this document:',
reset3: 'Web default account:',
reset4: 'SSH default account:',
@@ -89,6 +89,7 @@ const en = {
title: 'Image',
loading: 'Loading...',
empty: 'Nothing Found',
cdrom: 'Mount the image in CD-ROM mode',
mountFailed: 'Mount Failed',
mountDesc:
"In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.",
@@ -115,7 +116,7 @@ const en = {
runBackground: 'Run Background',
runFailed: 'Run failed',
attention: 'Attention',
delDesc: 'Are you sure to delete this file?',
delDesc: 'Are you sure you want to delete this file?',
confirm: 'Yes',
cancel: 'No',
delete: 'Delete',
@@ -175,7 +176,7 @@ const en = {
title: 'Device',
oled: {
title: 'OLED',
description: 'OLED screen automatically sleep',
description: 'Turn off OLED screen after',
0: 'Never',
15: '15 sec',
30: '30 sec',
@@ -204,11 +205,11 @@ const en = {
title: 'Tailscale',
memory: {
title: 'Memory optimization',
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. it's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. It's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
disable: 'Disable'
},
restart: 'Are you sure to restart Tailscale?',
stop: 'Are you sure to stop Tailscale?',
restart: 'Restart Tailscale?',
stop: 'Stop Tailscale?',
stopDesc: 'Log out Tailscale and disable automatic startup on boot.',
loading: 'Loading...',
notInstall: 'Tailscale not found! Please install.',
@@ -232,7 +233,7 @@ const en = {
deviceIP: 'Device IP',
account: 'Account',
logout: 'Logout',
logout2: 'Sure to logout?',
logout2: 'Confirm logout',
uninstall: 'Uninstall Tailscale',
okBtn: 'Yes',
cancelBtn: 'No'
@@ -242,7 +243,7 @@ const en = {
queryFailed: 'Get version failed',
updateFailed: 'Update failed. Please retry.',
isLatest: 'You already have the latest version.',
available: 'An update is available. Are you sure to update?',
available: 'An update is available. Are you sure you want to update now?',
updating: 'Update started. Please wait...',
confirm: 'Confirm',
cancel: 'Cancel'
@@ -254,6 +255,10 @@ const en = {
updateBtn: 'Change',
logoutBtn: 'Logout'
}
},
error: {
title: "We've ran into an issue",
refresh: 'Refresh'
}
}
};

View File

@@ -30,23 +30,23 @@ const fr = {
loginButtonText: 'Connexion',
tips: {
reset1:
'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.',
reset2: 'For detailed steps, please consult this document:',
reset3: 'Web default account:',
reset4: 'SSH default account:',
change1: 'Please note that this action will change the following passwords:',
change2: 'Web login password',
change3: 'System root password (SSH login password)',
change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.'
'Pour réinitialiser les mots de passe, appuyez et maintenez enfoncé le bouton BOOT sur le NanoKVM pendant 10 secondes.',
reset2: 'Pour les étapes détaillées, veuillez consulter ce document :',
reset3: 'Compte Web par défaut :',
reset4: 'Compte SSH par défaut :',
change1: 'Veuillez noter que cette action modifiera les mots de passe suivants :',
change2: 'Mot de passe de connexion Web',
change3: 'Mot de passe racine du système (mot de passe de connexion SSH)',
change4: 'Pour réinitialiser les mots de passe, appuyez et maintenez enfoncé le bouton BOOT sur le NanoKVM.'
}
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi for NanoKVM',
success: 'Please check the network status of NanoKVM and visit the new IP address.',
failed: 'Operation failed, please try again.',
description: 'Configurez le Wi-Fi pour le NanoKVM',
success: 'Veuillez vérifier le statut du réseau du NanoKVM et visitez la nouvelle adresse IP.',
failed: 'L\'opération a échoué, veuillez réessayer.',
confirmBtn: 'Ok',
finishBtn: 'Finished'
finishBtn: 'Terminé'
},
screen: {
video: 'Mode vidéo',
@@ -61,10 +61,10 @@ const fr = {
qualityHigh: 'Élevé',
qualityMedium: 'Moyen',
qualityLow: 'Bas',
frameDetect: 'Frame Detect',
frameDetect: 'Détection de trame',
frameDetectTip:
"Calcule la différence entre les images. Arrête la transmission du flux vidéo lorsqu'aucun changement n'est détecté sur l'écran de l'hôte distant",
resetHdmi: 'Reset HDMI'
resetHdmi: 'Réinitialiser le HDMI'
},
keyboard: {
paste: 'Coller',
@@ -85,7 +85,7 @@ const fr = {
absolute: 'Mode absolu',
relative: 'Mode relatif',
requestPointer:
'Utilisation du mode relatif. Veuillez cliquer sur le bureau pour obtenir le pointeur de la souris.',
'Pour utiliser le mode relatif, cliquez sur le bureau pour capturer le pointeur de la souris.',
resetHid: 'Réinitialiser le périphérique HID'
},
image: {
@@ -101,7 +101,7 @@ const fr = {
usb2: 'Assurez-vous que le disque virtuel est monté (Paramètres - Disque virtuel).',
usb3: 'Ouvrez le disque virtuel sur votre ordinateur et copiez le fichier image dans le répertoire racine du disque virtuel.',
scp1: 'Assurez-vous que le NanoKVM et votre ordinateur sont sur le même réseau local.',
scp2: 'Ouvrez un terminal sur votre ordinateur et utilisez la commande SCP pour télécharger le fichier image dans le répertoire /data du NanoKVM.',
scp2: 'Ouvrez un terminal sur votre ordinateur et utilisez la commande SCP pour copier le fichier image dans le répertoire /data du NanoKVM.',
scp3: 'Exemple : scp chemin-de-votre-image root@ip-de-votre-nanokvm:/data',
tfCard: 'Carte TF',
tf1: 'Cette méthode est adaptée aux systèmes Linux.',
@@ -114,11 +114,11 @@ const fr = {
script: {
title: 'Script',
upload: 'Téléverser',
run: 'Executer',
runBackground: 'Executer en arrière-plan',
run: 'Exécuter',
runBackground: 'Exécuter en arrière-plan',
runFailed: "Échec de l'exécution",
attention: 'Attention',
delDesc: 'Etes-vous sûr de vouloir supprimer ce fichier?',
delDesc: 'Êtes-vous sûr de vouloir supprimer ce fichier ?',
confirm: 'Oui',
cancel: 'Non',
delete: 'Supprimer',
@@ -142,38 +142,38 @@ const fr = {
},
power: {
title: 'Power',
reset: 'Reset',
reset: 'Réinitialiser',
power: 'Power',
powerShort: 'Power (appui court)',
powerLong: 'Power (appui long)'
},
settings: {
title: 'Settings',
title: 'Paramètres',
about: {
title: 'A propos de NanoKVM',
information: 'Informations',
ip: 'IP',
mdns: 'mDNS',
application: "Version de l'application",
applicationTip: 'NanoKVM web application version',
applicationTip: 'Version de l\'application Web NanoKVM',
image: "Version de l'image",
imageTip: 'NanoKVM system image version',
imageTip: 'Version de l\'image système NanoKVM',
deviceKey: "Clé de l'appareil",
community: 'Communauté'
},
appearance: {
title: 'Appearance',
display: 'Display',
language: 'Language',
menuBar: 'Menu Bar',
menuBarDesc: 'Display icons in the menu bar'
title: 'Apparence',
display: 'Affichage',
language: 'Langue',
menuBar: 'Barre de menus',
menuBarDesc: 'Afficher les icônes dans la barre de menus'
},
device: {
title: 'Device',
title: 'Appareil',
oled: {
title: 'OLED',
description: 'OLED screen automatically sleep',
0: 'Never',
description: 'Écran OLED s\'éteint automatiquement',
0: 'Jamais',
15: '15 sec',
30: '30 sec',
60: '1 min',
@@ -181,36 +181,36 @@ const fr = {
300: '5 min',
600: '10 min',
1800: '30 min',
3600: '1 hour'
3600: '1 heure'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
description: 'Configurez le Wi-Fi',
setBtn: 'Configurer'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
disk: 'Disque virtuel',
diskDesc: 'Monter le disque virtuel U sur l\'hôte distant',
network: 'Réseau virtuel',
networkDesc: 'Monter la carte réseau virtuelle sur l\'hôte distant'
},
tailscale: {
title: 'Tailscale',
memory: {
title: 'Memory optimization',
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. it's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
disable: 'Disable'
title: 'Optimisation de la mémoire',
tip: "Lorsque l'utilisation de la mémoire dépasse la limite, la collecte des ordures est effectuée plus agressivement pour essayer de libérer de la mémoire. Il est recommandé de définir à 50MB si vous utilisez Tailscale. Un redémarrage de Tailscale est nécessaire pour que le changement prenne effet.",
disable: 'Désactiver'
},
restart: 'Are you sure to restart Tailscale?',
stop: 'Are you sure to stop Tailscale?',
stopDesc: 'Log out Tailscale and disable its automatic startup on boot.',
restart: 'Êtes-vous sûr de vouloir redémarrer Tailscale ?',
stop: 'Êtes-vous sûr de vouloir arrêter Tailscale ?',
stopDesc: 'Arrêtez Tailscale et désactivez son démarrage automatique.',
loading: 'Chargement...',
notInstall: "Tailscale non trouvé! Veuillez l'installer.",
notInstall: "Tailscale non trouvé ! Veuillez l'installer.",
install: 'Installer',
installing: 'Installation',
failed: 'Installation échouée',
retry: "Veuillez rafraîchir et réessayer. Ou essayez d'installer manuellement",
download: 'Télécharger le',
package: 'installation package',
package: 'paquet d\'installation',
unzip: 'et décompressez-le',
upTailscale: 'Téléverser tailscale dans le répertoire NanoKVM /usr/sbin/',
upTailscaled: 'Téléverser tailscaled dans le répertoire NanoKVM /usr/sbin/',
@@ -225,13 +225,13 @@ const fr = {
account: 'Compte',
logout: 'Déconnexion',
logout2: 'Voulez-vous vous déconnecter?',
okBtn: 'Yes',
cancelBtn: 'No'
okBtn: 'Oui',
cancelBtn: 'Non'
},
update: {
title: 'Vérifier les mises à jour',
queryFailed: 'Impossible de vérifier les mises à jour. Veuillez réessayer.',
updateFailed: 'Mis à jour échouée. Veuillez réessayer.',
updateFailed: 'Mise à jour échouée. Veuillez réessayer.',
isLatest: 'Vous avez déjà la dernière version.',
available: 'Une mise à jour est disponible. Voulez-vous vraiment mettre à jour?',
updating: 'Mise à jour en cours. Veuillez patienter...',
@@ -239,11 +239,11 @@ const fr = {
cancel: 'Annuler'
},
account: {
title: 'Account',
webAccount: 'Web Account Name',
password: 'Password',
updateBtn: 'Update',
logoutBtn: 'Logout'
title: 'Compte',
webAccount: 'Nom du compte Web',
password: 'Mot de passe',
updateBtn: 'Mettre à jour',
logoutBtn: 'Déconnexion'
}
}
}

View File

@@ -137,6 +137,12 @@ const ko = {
input: 'MAC주소를 입력하세요.',
ok: '확인'
},
download: {
title: '이미지 다운로드',
input: '원격 이미지 URL을 입력하세요.',
ok: '확인',
disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.'
},
power: {
title: '전원',
reset: '리셋',
@@ -144,12 +150,6 @@ const ko = {
powerShort: '전원 (짧게 누르기)',
powerLong: '전원 (길게 누르기)'
},
download: {
download: '이미지 다운로드',
input: '원격 이미지 URL을 입력하세요.',
ok: '확인',
disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.'
},
settings: {
title: '설정',
about: {
@@ -191,6 +191,10 @@ const ko = {
description: 'Wi-Fi 설정',
setBtn: '설정'
},
ssh: {
description: 'SSH 원격 접속 활성화',
tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)'
},
disk: '가상 디스크',
diskDesc: '원격 호스트에서 가상 U-디스크를 마운트합니다.',
network: '가상 네트워크',
@@ -228,6 +232,7 @@ const ko = {
account: '계정',
logout: '로그아웃',
logout2: '정말로 로그아웃 합니까?',
uninstall: 'Tailscale 제거',
okBtn: '예',
cancelBtn: '아니오'
},

View File

@@ -19,8 +19,7 @@ const ru = {
invalidUser: 'Неверное имя пользователя или пароль',
error: 'Непредвиденная ошибка',
changePassword: 'Изменить пароль',
changePasswordDesc:
'Для безопасности вашего устройства измените, пожалуйста, пароль веб-входа.',
changePasswordDesc: 'Для безопасности вашего устройства, пожалуйста поменяйте пароль!',
differentPassword: 'Пароли не совпадают',
illegalUsername: 'Имя пользователя содержит недопустимые символы',
illegalPassword: 'Пароль содержит недопустимые символы',
@@ -30,23 +29,23 @@ const ru = {
loginButtonText: 'Войти',
tips: {
reset1:
'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.',
reset2: 'For detailed steps, please consult this document:',
reset3: 'Web default account:',
reset4: 'SSH default account:',
change1: 'Please note that this action will change the following passwords:',
change2: 'Web login password',
change3: 'System root password (SSH login password)',
change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.'
'Чтобы сбросить пароли, нажмите и удерживайте кнопку BOOT на NanoKVM в течении 10 секунд.',
reset2: 'Подробные шаги описаны в этом документе:',
reset3: 'Аккаунт веб-интерфейса по умолчанию:',
reset4: 'Аккаунт SSH по умолчанию:',
change1: 'Пожалуйста учтите, что это действие изменит следующие пароли:',
change2: 'Пароль для входа в веб-интерфейс',
change3: 'Пароль системного пользователя "root" (пароль для входа по SSH)',
change4: 'Чтобы сбросить пароли, нажмите и удерживайте кнопку BOOT на NanoKVM.'
}
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi for NanoKVM',
success: 'Please check the network status of NanoKVM and visit the new IP address.',
failed: 'Operation failed, please try again.',
confirmBtn: 'Ok',
finishBtn: 'Finished'
description: 'Настройка Wi-Fi для NanoKVM',
success: 'Пожалуйста, проверьте сетевой статус NanoKVM и откройте в браузере новый IP адрес.',
failed: 'Операция не удалась, пожалуйста попробуйте снова.',
confirmBtn: 'OK',
finishBtn: 'Готово'
},
screen: {
video: 'Видеорежим',
@@ -64,13 +63,13 @@ const ru = {
frameDetect: 'Экономия трафика',
frameDetectTip:
'Вычисляет разницу между кадрами и прекращает передачу видеопотока, если на экране удаленного узла не обнаружено никаких изменений.',
resetHdmi: 'Reset HDMI'
resetHdmi: 'Перезагрузить HDMI подсистему'
},
keyboard: {
paste: 'вставить',
tips: 'Только стандартные буквы и символы клавиатуры поддерживаются',
placeholder: 'Пожалуйста, введите',
submit: 'подтверждать',
paste: 'Вставка текста',
tips: 'Поддерживаются только стандартные буквы и символы клавиатуры',
placeholder: 'Текст для ввода',
submit: 'Вставить',
virtual: 'Клавиатура',
ctrlaltdel: 'Ctrl+Alt+Del'
},
@@ -139,6 +138,12 @@ const ru = {
input: 'Введите MAC-адрес',
ok: 'ОК'
},
download: {
title: 'Скачать образ',
input: 'Введите адрес удаленного образа',
ok: 'OK',
disabled: 'Невозможно скачать образ, раздел /data находится в режиме только для чтения'
},
power: {
title: 'Питание',
reset: 'Экстренная перезагрузка',
@@ -147,61 +152,65 @@ const ru = {
powerLong: 'Питание (длинное нажатие)'
},
settings: {
title: 'Settings',
title: 'Настройки',
about: {
title: 'О системе NanoKVM',
information: 'Информация',
ip: 'IP-адрес',
mdns: 'Доменное имя mDNS',
application: 'Версия ПО',
applicationTip: 'NanoKVM web application version',
applicationTip: 'Версия веб-программы NanoKVM',
image: 'Версия прошивки',
imageTip: 'NanoKVM system image version',
imageTip: 'Версия системного образа NanoKVM',
deviceKey: 'Ключ устройства',
community: 'Сообщество'
},
appearance: {
title: 'Appearance',
display: 'Display',
language: 'Language',
menuBar: 'Menu Bar',
menuBarDesc: 'Display icons in the menu bar'
title: 'Внешний вид',
display: 'Отображение',
language: 'Язык',
menuBar: 'Панель меню',
menuBarDesc: 'Иконки для отображения в панели меню'
},
device: {
title: 'Device',
title: 'Устройство',
oled: {
title: 'OLED',
description: 'OLED screen automatically sleep',
0: 'Never',
15: '15 sec',
30: '30 sec',
60: '1 min',
180: '3 min',
300: '5 min',
600: '10 min',
1800: '30 min',
3600: '1 hour'
title: 'OLED экран',
description: 'Автоматическое отключение OLED экрана',
0: 'Никогда',
15: '15 сек',
30: '30 сек',
60: '1 мин',
180: '3 мин',
300: '5 мин',
600: '10 мин',
1800: '30 мин',
3600: '1 час'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
description: 'Настройка Wi-Fi',
setBtn: 'Конфигурация'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
ssh: {
description: 'Включить удаленный доступ по SSH',
tip: 'Задайте сильный пароль перед включением (Аккаунт - Пароль - Обновить)'
},
disk: 'Виртуальный диск',
diskDesc: 'Смонтировать виртуальный USB диск на удаленном хосте',
network: 'Виртуальная сеть',
networkDesc: 'Смонтировать виртуальное сетевое устройство на удаленном хосте'
},
tailscale: {
title: 'Tailscale',
memory: {
title: 'Memory optimization',
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. it's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
disable: 'Disable'
title: 'Оптимизация памяти',
tip: "Когда использование ОЗУ выходит за пределы, очистка мусора происходит более агрессивно, дабы попытаться освободить память. Рекомендуется задать на 50MB при использовании Tailscale. Перезагрузка Tailscale рекомендуется для вступления данной настройки в силу.",
disable: 'Отключить'
},
restart: 'Are you sure to restart Tailscale?',
stop: 'Are you sure to stop Tailscale?',
stopDesc: 'Log out Tailscale and disable its automatic startup on boot.',
restart: 'Вы уверены что хотите перезагрузить Tailscale?',
stop: 'Вы уверены что хотите остановить Tailscale?',
stopDesc: 'Выйти из Tailscale и отключить его автоматический запуск при включении.',
loading: 'Загрузка...',
notInstall: 'Tailscale не найден! Пожалуйста, установите.',
install: 'Установить',
@@ -221,28 +230,29 @@ const ru = {
enable: 'Включить Tailscale',
deviceName: 'Имя устройства',
deviceIP: 'IP адрес устройства',
account: 'аккаунт',
account: 'Аккаунт',
logout: 'Выход',
logout2: 'Вы действительно хотите выйти?',
okBtn: 'Yes',
cancelBtn: 'No'
uninstall: 'Удалить Tailscale',
okBtn: 'Да',
cancelBtn: 'Нет'
},
update: {
title: 'Проверить обновления',
queryFailed: 'Получить версию не удалось',
updateFailed: 'Обновление не удалось. Пожалуйста, попробуйте еще раз.',
isLatest: 'У вас уже есть последняя версия.',
isLatest: 'У вас уже последняя версия.',
available: 'Доступно обновление. Вы уверены, что хотите обновить?',
updating: 'Начато обновление. Пожалуйста, подождите...',
confirm: 'Подтвердить',
cancel: 'Отмена'
},
account: {
title: 'Account',
webAccount: 'Web Account Name',
password: 'Password',
updateBtn: 'Update',
logoutBtn: 'Logout'
title: 'Аккаунт',
webAccount: 'Имя веб-аккаунта',
password: 'Пароль',
updateBtn: 'Обновить',
logoutBtn: 'Выйти'
}
}
}

261
web/src/i18n/locales/th.ts Normal file
View File

@@ -0,0 +1,261 @@
const en = {
translation: {
head: {
desktop: 'Remote Desktop',
login: 'เข้าสู่ระบบ',
changePassword: 'เปลี่ยนรหัสผ่าน',
terminal: 'Terminal',
wifi: 'Wi-Fi'
},
auth: {
login: 'เข้าสู่ระบบ',
placeholderUsername: 'ชื่อผู้ใช้งาน',
placeholderPassword: 'รห้สผ่าน',
placeholderPassword2: 'ใส่รหัสผ่านอีกครั้ง',
noEmptyUsername: 'ต้องใส่ชื่อผู้ใช้งาน',
noEmptyPassword: 'ต้องใส้รหัสผ่าน',
noAccount: 'ไม่สามารถอ่านข้อมูลได้, โปรดรีเฟรชหน้าเว็บหรือรีเซ็ตรหัสผ่าน',
invalidUser: 'ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง',
error: 'เกิดข้อผิดพลาด',
changePassword: 'เปลี่ยนรหัสผ่าน',
changePasswordDesc: 'เพื่อความปลอดภัย, โปรดเปลี่ยนรหัสผ่าน!',
differentPassword: 'รหัสผ่านไม่ตรงกัน',
illegalUsername: 'ชื่อผู้ใช้ไม่สามารถมีอักขระที่ไม่ถูกต้อง',
illegalPassword: 'รหัสผ่านไม่สามารถมีอักขระที่ไม่ถูกต้อง',
forgetPassword: 'ลืมรหัสผ่าน',
ok: 'โอเค',
cancel: 'ยกเลิก',
loginButtonText: 'เข้าสู่ระบบ',
tips: {
reset1:
'เพื่อรีเช็ทรหัสผ่าน, กดปุ่ม BOOT ค้างไว้ 10 วินาที.',
reset2: 'สำหรับวิธีแบบลงลึก โปรดอ่าน::',
reset3: 'ค่าเริ่มต้นบัญชีเว็บ:',
reset4: 'ค่าเริ่มต้นบัญชี SSH:',
change1: 'โปรดทราบ การกระทำนี้จะเปลี่ยนรหัสดีงกล่าว:',
change2: 'รหัสผ่านเว็บไชต์',
change3: 'รหัส Root ของระบบ (ใช้เข้า SSH)',
change4: 'เพื่อรีเช็ทรหัสผ่าน, กรุณากดปุ่ม BOOT บนอุปกรณ์ NanoKVM ค้างไว้.'
}
},
wifi: {
title: 'Wi-Fi',
description: 'ตั้งค้า Wi-Fi สำหรับ NanoKVM',
success: 'กรุณาตรวจสอบสถานะเครื่องข่ายของ NanoKVM และไปที่ IP address ใหม่',
failed: 'เกิดข้อผิดพลาด กรุณาลองใหม่.',
confirmBtn: 'โอเค',
finishBtn: 'เสร็จ'
},
screen: {
video: 'โหมดวีดีโอ',
resolution: 'ความคมชัด',
auto: 'อัตโนมัติ',
autoTips:
"อาการภาพฉีกขาดหรือเมาส์ไม่ตรงตำแหน่งอาจเกิดขึ้นที่ความละเอียดบางระดับ แนะนำให้ปรับความละเอียดของคอมพิวเตอร์ต้นทางหรือปิดโหมดอัตโนมัติ",
fps: 'FPS',
customizeFps: 'กำหนดเอง',
quality: 'คุณภาพ',
qualityLossless: 'ไม่สูญเสียคุณภาพ',
qualityHigh: 'สูง',
qualityMedium: 'กลาง',
qualityLow: 'ต่ำ',
frameDetect: 'ตรวจจับเฟรม',
frameDetectTip:
"ระบบจะคำนวณความแตกต่างระหว่างเฟรม และหยุดส่งสตรีมวิดีโอเมื่อไม่พบการเปลี่ยนแปลงบนหน้าจอของคอมพิวเตอร์ต้นทาง",
resetHdmi: 'รีเช็ท HDMI'
},
keyboard: {
paste: 'วาง',
tips: 'รองรับเฉพาะตัวอักษรและสัญลักษณ์มาตรฐานของแป้นพิมพ์เท่านั้น',
placeholder: 'กรุณาป้อน',
submit: 'ยืนยัน',
virtual: 'คีบอร์ด',
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
default: 'ตัวชี้เริ่มต้น',
pointer: 'ตัวชี้แบบชี้',
cell: 'ตัวชี้แบบเซลล์',
text: 'ตัวชี้แบบข้อความ',
grab: 'ตัวชี้แบบจับ',
hide: 'ซ่อนตัวชี้',
mode: 'โหมดเมาส์',
absolute: 'โหมดสัมบูรณ์',
relative: 'โหมดสัมพัทธ์',
requestPointer: 'กำลังใช้โหมดสัมพัทธ์ กรุณาคลิกที่เดสก์ท็อปเพื่อรับตัวชี้เมาส์',
resetHid: 'รีเซ็ต HID'
},
image: {
title: 'ดิสก์จำลอง',
loading: 'กำลังโหลด...',
empty: 'ไม่พบดิสก์',
mountFailed: 'การติดตั้งล้มเหลว',
mountDesc:
"ในบางระบบ, จะต้อง eject ดิสก์จำลองก่อนที่จะใส่ดิสก์ใหม่.",
tips: {
title: 'วิธีการอัปโหลด',
usb1: 'เชื่อมต่อ NanoKVM กับคอมพิวเตอร์ของคุณผ่าน USB',
usb2: 'ตรวจสอบให้แน่ใจว่าได้เปิดใช้งานดิสก์เสมือนแล้ว (ตั้งค่า - ดิสก์เสมือน)',
usb3: 'เปิดดิสก์บนคอมพิวเตอร์ของคุณและคัดลอกไฟล์อิมเมจไปยังไดเรกทอรีหลักของดิสก์',
scp1: 'ตรวจสอบให้แน่ใจว่า NanoKVM และคอมพิวเตอร์ของคุณอยู่ในเครือข่ายเดียวกัน',
scp2: 'เปิดเทอร์มินัลบนคอมพิวเตอร์ของคุณและใช้คำสั่ง SCP เพื่ออัปโหลดไฟล์อิมเมจไปยังไดเรกทอรี /data บน NanoKVM',
scp3: 'ตัวอย่าง: scp <ที่อยู่ของไฟล์> root@<ip อุปกรณ์>:/data',
tfCard: 'การ์ด TF',
tf1: 'วิธีนี้รองรับแค่บนระบบปฏิบัติการ Linux',
tf2: 'นำการ์ด TF ออกจาก NanoKVM',
tf3: 'ใส่การ์ด TF ลงในเครื่องอ่านการ์ดและเชื่อมต่อกับคอมพิวเตอร์ของคุณ',
tf4: 'คัดลอกไฟล์อิมเมจไปยังไดเรกทอรี /data บนการ์ด TF',
tf5: 'ใส่การ์ด TF กลับเข้า NanoKVM'
}
},
script: {
title: 'สคริปต์',
upload: 'อัปโหลด',
run: 'เรียกใช้',
runBackground: 'เรียกใช้ในพื้นหลัง',
runFailed: 'การเรียกใช้ล้มเหลว',
attention: 'ความสนใจ',
delDesc: 'คุณแน่ใจที่จะลบไฟล์นี้หรือไม่?',
confirm: 'ใช่',
cancel: 'ไม่ใช่',
delete: 'ลบ',
close: 'ปิด'
},
terminal: {
title: 'เทอร์มินอล',
nanokvm: 'เทอร์มินอลของ NanoKVM',
serial: 'เทอร์มินอลของ Serial Port',
serialPort: 'Serial Port',
serialPortPlaceholder: 'กรุณาเลือก serial port',
baudrate: 'Baud rate',
confirm: 'ยืนยัน'
},
wol: {
title: 'Wake-on-LAN',
sending: 'กำลังส่งคำสั่ง...',
sent: 'ส่งคำสั่งแล้ว',
input: 'กรุณาใส่ MAC Address ของเครื่องที่ต้องการปลุก',
ok: 'ยืนยัน'
},
download: {
title: 'ดาวโหลด Disk Image',
input: 'กรุณาใส่ลิงค์ของ Disk Image.',
ok: 'ยืนยัน',
disabled: '/data มีการตั้งค่าเป็นอ่านอย่างเดียว ดังนั้นเราไม่สามารถดาวโหลด Disk Image ได้'
},
power: {
title: 'เปิด/ปิด',
reset: 'รีเช็ท',
power: 'เปิด/ปิด',
powerShort: 'เปิด/ปิด (กดปล่อย)',
powerLong: 'เปิด/ปิด (กดค้าง)'
},
settings: {
title: 'การตั้งค่า',
about: {
title: 'เกี๋ยวกับ NanoKVM',
information: 'ข้อมูล',
ip: 'IP',
mdns: 'mDNS',
application: 'เวอร์ชั่นแอปพิเคชั่น',
applicationTip: 'เวอร์ชั่นเว็บ NanoKVM',
image: 'เวอร์ชั่น Image ระบบ',
imageTip: 'เวอร์ชั่น Image ระบบ NanoKVM',
deviceKey: 'คีย์อุปกรณ์',
community: 'ชุมชน'
},
appearance: {
title: 'ลักษณะการแสดงผล',
display: 'การแสดงผล',
language: 'ภาษา',
menuBar: 'แถบเมนู',
menuBarDesc: 'แสดงไอคอนในแถบเมนู'
},
device: {
title: 'อุปกรณ์',
oled: {
title: 'OLED',
description: 'เวลาก่อนจอ OLED จะปิดอัตโนมัติ',
0: 'ไม่เลย',
15: '15 วินาที',
30: '30 วินาที',
60: '1 นาที',
180: '3 นาทร',
300: '5 นาที',
600: '10 นาที',
1800: '30 นาที',
3600: '1 ชั่วโมง'
},
wifi: {
title: 'Wi-Fi',
description: 'ตั้งค่า Wi-Fi',
setBtn: 'ยืนยัน'
},
ssh: {
description: 'เปิดใช้งาน SSH',
tip: 'ตั้งรหัสผ่านทีปลอดถัยก่อนเปิดใช้งาน (บัญชี - เปลี่ยนรหัสผ่าน)'
},
disk: 'ดิสก์จำลอง',
diskDesc: 'เปิดใช้งาน U-disk จำลอง',
network: 'เครือข่ายจำลอง',
networkDesc: 'เปิดใช้งานอุปกรณ์เครือข่ายจำลอง'
},
tailscale: {
title: 'Tailscale',
memory: {
title: 'การปรับแต่งหน่วยความจำ',
tip: "เมื่อการใช้งานหน่วยความจำเกินขีดจำกัด การเก็บขยะจะทำงานอย่างเข้มงวดมากขึ้นเพื่อพยายามปล่อยหน่วยความจำ คำแนะนำคือการตั้งค่าเป็น 50MB หากใช้ Tailscale และต้องทำการรีสตาร์ท Tailscale เพื่อให้การเปลี่ยนแปลงมีผล",
disable: 'ปิดใช้งาน'
},
restart: 'คุณแน่ใจที่จะรีสตาร์ท Tailscale หรือไม่?',
stop: 'คุณแน่ใจที่จะปิด Tailscale หรือไม่?',
stopDesc: 'ออกจากระบบ Tailscale และปิดใช้งานการเริ่มต้นอัตโนมัติเมื่อบูตเครื่อง',
loading: 'กำลังโหลด...',
notInstall: 'ไม่พบ Tailscale! กรุณาติดตั้ง',
install: 'ติดตั้ง',
installing: 'กำลังติดตั้ง',
failed: 'การติดตั้งล้มเหลว',
retry: 'กรุณารีเฟรชและลองใหม่ หรือพยายามติดตั้งด้วยตนเอง',
download: 'ดาวน์โหลด',
package: 'แพ็กเกจการติดตั้ง',
unzip: 'และแตกไฟล์',
upTailscale: 'อัปโหลด tailscale ไปยังไดเรกทอรี /usr/bin/ ของ NanoKVM',
upTailscaled: 'อัปโหลด tailscaled ไปยังไดเรกทอรี /usr/sbin/ ของ NanoKVM',
refresh: 'รีเฟรชหน้าปัจจุบัน',
notLogin: 'อุปกรณ์ยังไม่ได้ผูกบัญชี กรุณาล็อกอินและผูกอุปกรณ์นี้กับบัญชีของคุณ',
urlPeriod: 'ลิงก์นี้ใช้ได้ 10 นาที',
login: 'ล็อกอิน',
loginSuccess: 'ล็อกอินสำเร็จ',
enable: 'เปิดใช้งาน Tailscale',
deviceName: 'ชื่ออุปกรณ์',
deviceIP: 'IP อุปกรณ์',
account: 'บัญชีผู้ใช้',
logout: 'ออกจากระบบ',
logout2: 'คุณแน่ใจที่จะออกจากระบบหรือไม่?',
uninstall: 'ถอนการติดตั้ง Tailscale',
okBtn: 'ใช่',
cancelBtn: 'ไม่ใช่'
},
update: {
title: 'ตรวจสอบการอัปเดต',
queryFailed: 'ไม่สามารถรับข้อมูลเวอร์ชันได้',
updateFailed: 'การอัปเดตล้มเหลว กรุณาลองใหม่',
isLatest: 'คุณมีเวอร์ชันล่าสุดแล้ว',
available: 'มีการอัปเดตใหม่ คุณแน่ใจที่จะอัปเดตหรือไม่?',
updating: 'กำลังเริ่มอัปเดต กรุณารอสักครู่...',
confirm: 'ยืนยัน',
cancel: 'ยกเลิก'
},
account: {
title: 'บัญชี',
webAccount: 'ชื่อผู้ใช้',
password: 'รหัสผ่าน',
updateBtn: 'ยืนยัน',
logoutBtn: 'ออกจากระบบ'
}
}
}
};
export default en;

View File

@@ -87,6 +87,7 @@ const zh = {
title: '镜像',
loading: '加载中',
empty: '无镜像文件',
cdrom: '以 CD-ROM 模式挂载镜像',
mountFailed: '挂载失败',
mountDesc: '在某些系统中,需要在远程主机中弹出虚拟硬盘后再挂载镜像。',
tips: {

View File

@@ -134,6 +134,12 @@ const zh_tw = {
input: '請輸入 MAC 位址',
ok: '確定'
},
download: {
title: '下载映像',
input: '請輸入映像檔的下載URL',
ok: '確定',
disabled: '/data 為唯讀目錄,無法下載映像檔'
},
power: {
title: '電源',
reset: '重新啟動',
@@ -182,6 +188,10 @@ const zh_tw = {
description: '設定 Wi-Fi',
setBtn: '設定'
},
ssh: {
description: '啟用SSH',
tip: '啟用前請務必設定強密碼(帳號 - 更改密碼)'
},
disk: '虛擬隨身碟',
diskDesc: '在遠端主機上連接虛擬隨身碟',
network: '虛擬網卡',
@@ -219,6 +229,7 @@ const zh_tw = {
account: '帳號',
logout: '登出',
logout2: '確認登出?',
uninstall: '移除 Tailscale',
okBtn: '確認',
cancelBtn: '取消'
},

View File

@@ -14,6 +14,8 @@ import { Tips } from './tips.tsx';
export const Login = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const [isLoading, setIsloading] = useState(false);
const [msg, setMsg] = useState('');
useEffect(() => {
@@ -29,6 +31,9 @@ export const Login = () => {
}, [msg]);
function login(values: any) {
if (isLoading) return;
setIsloading(true);
const username = values.username;
const password = encrypt(values.password);
@@ -48,6 +53,9 @@ export const Login = () => {
})
.catch(() => {
setMsg(t('auth.error'));
})
.finally(() => {
setIsloading(false);
});
}
@@ -84,7 +92,7 @@ export const Login = () => {
<div className="text-red-500">{msg}</div>
<Form.Item>
<Button type="primary" htmlType="submit" className="w-full">
<Button type="primary" htmlType="submit" className="w-full" loading={isLoading}>
{t('auth.loginButtonText')}
</Button>
</Form.Item>

View File

@@ -14,10 +14,11 @@ import * as api from '@/api/storage.ts';
import { client } from '@/lib/websocket.ts';
type ImagesProps = {
cdrom: boolean;
setIsMounted: (isMounted: boolean) => void;
};
export const Images = ({ setIsMounted }: ImagesProps) => {
export const Images = ({ cdrom, setIsMounted }: ImagesProps) => {
const { t } = useTranslation();
const [notify, contextHolder] = notification.useNotification();
@@ -77,7 +78,7 @@ export const Images = ({ setIsMounted }: ImagesProps) => {
const filename = mountedImage === image ? '' : image;
api
.mountImage(filename)
.mountImage(filename, cdrom)
.then((rsp) => {
if (rsp.code !== 0) {
openNotification();

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react';
import { Divider, Popover } from 'antd';
import { Divider, Popover, Switch, Tooltip } from 'antd';
import clsx from 'clsx';
import { DiscIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useMediaQuery } from 'react-responsive';
import { getMountedImage } from '@/api/storage.ts';
import { getCdRom, getMountedImage } from '@/api/storage.ts';
import { Images } from './images.tsx';
import { Tips } from './tips.tsx';
@@ -16,24 +16,40 @@ export const Image = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const [cdrom, setCdrom] = useState(false);
useEffect(() => {
getMountedImage().then((rsp) => {
if (rsp.code !== 0) return;
setIsMounted(!!rsp.data?.file);
});
getCdRom().then((rsp) => {
if (rsp.code !== 0) return;
setCdrom(rsp.data?.cdrom === 1);
});
}, []);
const content = (
<div className="min-w-[300px]">
<div className="flex items-center justify-between px-1">
<span className="text-base font-bold text-neutral-300">{t('image.title')}</span>
<Tips />
<div className="flex items-center space-x-1">
<span className="text-base font-bold text-neutral-300">{t('image.title')}</span>
<Tips />
</div>
<Tooltip title={t('image.cdrom')} placement="bottom">
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-400">CD-ROM</span>
<Switch size="small" checked={cdrom} onChange={(checked) => setCdrom(checked)}></Switch>
</div>
</Tooltip>
</div>
<Divider style={{ margin: '10px 0 15px 0' }} />
{isPopoverOpen && <Images setIsMounted={setIsMounted} />}
{isPopoverOpen && <Images cdrom={cdrom} setIsMounted={setIsMounted} />}
</div>
);

View File

@@ -51,13 +51,13 @@ export const Tips = () => {
return (
<>
<div
className="flex cursor-pointer items-center space-x-1 text-neutral-400"
className="flex cursor-pointer items-center space-x-1 text-neutral-400 hover:text-blue-400"
onClick={() => setIsModalOpen(true)}
>
<CircleHelpIcon size={16} />
<span className="text-sm text-neutral-500 hover:text-neutral-400">
{t('image.tips.title')}
</span>
{/*<span className="text-sm text-neutral-500 hover:text-neutral-400">*/}
{/* {t('image.tips.title')}*/}
{/*</span>*/}
</div>
<Modal

View File

@@ -68,7 +68,7 @@ export const Update = ({ setIsLocked }: UpdateProps) => {
setErrMsg('');
window.location.reload();
}, 6000);
}, 12000);
});
}
@@ -114,6 +114,17 @@ export const Update = ({ setIsLocked }: UpdateProps) => {
)}
{status === 'failed' && <Result subTitle={errMsg} />}
<div className="flex justify-center">
<Button
type="link"
size="small"
href="https://github.com/sipeed/NanoKVM/blob/main/CHANGELOG.md"
target="_blank"
>
CHANGELOG
</Button>
</div>
</>
);
};