init commit
This commit is contained in:
commit
a00e135687
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Плоттер серийного порта
|
||||||
|
|
||||||
|
https://chat.deepseek.com/a/chat/s/5091a6a6-862f-406c-8c8b-fbd9bec4e303
|
||||||
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
1. Добавить окно выбора серийного порта и скорости подключения
|
||||||
|
2. Обновлять информацию из серийного порта -> горутина с каналом на обновление и получения из update
|
||||||
|
3. Парсер значений и их отрисовка различными цветами
|
||||||
|
4. Запуск аргументами командной строки или конфигом
|
||||||
|
5. Отслеживание времени
|
||||||
|
6. Добавить функцию записи данных в файл
|
||||||
|
|
197
cmd/plotter/main.go
Normal file
197
cmd/plotter/main.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"go.bug.st/serial/enumerator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
values []float64 // Data points to plot
|
||||||
|
width int // Width of the plot
|
||||||
|
height int // Height of the plot
|
||||||
|
time float64 // Time variable for animation
|
||||||
|
}
|
||||||
|
type TickMsg time.Time
|
||||||
|
|
||||||
|
func doTick() tea.Cmd {
|
||||||
|
return tea.Tick(time.Second/30, func(t time.Time) tea.Msg {
|
||||||
|
return TickMsg(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the model with some data
|
||||||
|
func initialModel() model {
|
||||||
|
return model{
|
||||||
|
values: make([]float64, 100), // More points for higher resolution
|
||||||
|
width: 70,
|
||||||
|
height: 25,
|
||||||
|
time: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Init() tea.Cmd {
|
||||||
|
return doTick()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update function (required by bubbletea)
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "q", "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
case TickMsg:
|
||||||
|
|
||||||
|
// Update the time variable to animate the sine wave
|
||||||
|
m.time += 0.1
|
||||||
|
// Generate new sine wave values
|
||||||
|
for i := range m.values {
|
||||||
|
m.values[i] = math.Sin(float64(i)/10.0 + m.time)
|
||||||
|
}
|
||||||
|
return m, doTick()
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate between two colors based on a factor (0 to 1)
|
||||||
|
func interpolateColor(color1, color2 lipgloss.Color, factor float64) lipgloss.Color {
|
||||||
|
r1, g1, b1 := hexToRGB(string(color1))
|
||||||
|
r2, g2, b2 := hexToRGB(string(color2))
|
||||||
|
r := uint8(float64(r1) + (float64(r2)-float64(r1))*factor)
|
||||||
|
g := uint8(float64(g1) + (float64(g2)-float64(g1))*factor)
|
||||||
|
b := uint8(float64(b1) + (float64(b2)-float64(b1))*factor)
|
||||||
|
return lipgloss.Color(fmt.Sprintf("#%02X%02X%02X", r, g, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert hex color to RGB values
|
||||||
|
func hexToRGB(hex string) (uint8, uint8, uint8) {
|
||||||
|
var r, g, b uint8
|
||||||
|
fmt.Sscanf(hex, "#%02X%02X%02X", &r, &g, &b)
|
||||||
|
return r, g, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// View function (required by bubbletea)
|
||||||
|
func (m model) View() string {
|
||||||
|
// Create a 2D grid to represent the plot
|
||||||
|
grid := make([][]string, m.height)
|
||||||
|
for i := range grid {
|
||||||
|
grid[i] = make([]string, m.width)
|
||||||
|
for j := range grid[i] {
|
||||||
|
grid[i][j] = " " // Initialize with spaces
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize values to fit within the plot height
|
||||||
|
maxValue := 1.0 // Sine wave ranges from -1 to 1
|
||||||
|
minValue := -1.0
|
||||||
|
scale := float64(m.height-1) / (maxValue - minValue)
|
||||||
|
|
||||||
|
// Define the gradient colors
|
||||||
|
colorGreen := lipgloss.Color("#00FF00") // Green
|
||||||
|
colorOrange := lipgloss.Color("#FF7F00") // Orange
|
||||||
|
colorRed := lipgloss.Color("#FF0000") // Red
|
||||||
|
|
||||||
|
// Plot the values with higher resolution and color
|
||||||
|
for x, value := range m.values {
|
||||||
|
y := int((value - minValue) * scale)
|
||||||
|
if y >= 0 && y < m.height && x < m.width {
|
||||||
|
// Calculate the interpolation factor based on the y position
|
||||||
|
factor := float64(y) / float64(m.height-1)
|
||||||
|
var color lipgloss.Color
|
||||||
|
if factor < 0.5 {
|
||||||
|
// Interpolate between Green and Orange
|
||||||
|
color = interpolateColor(colorGreen, colorOrange, factor*2)
|
||||||
|
} else {
|
||||||
|
// Interpolate between Orange and Red
|
||||||
|
color = interpolateColor(colorOrange, colorRed, (factor-0.5)*2)
|
||||||
|
}
|
||||||
|
style := lipgloss.NewStyle().Foreground(color)
|
||||||
|
// Use a thinner character for the line
|
||||||
|
grid[m.height-1-y][x] = style.Render("│") // Vertical bar for a thin line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a horizontal line at y = 0.0
|
||||||
|
yZero := int((0.0 - minValue) * scale) // Calculate the y position for 0.0
|
||||||
|
if yZero >= 0 && yZero < m.height {
|
||||||
|
for x := 0; x < m.width; x++ {
|
||||||
|
// Use a horizontal line character
|
||||||
|
grid[m.height-1-yZero][x] = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).Render("─")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the grid to a string with axis labels
|
||||||
|
var plot string
|
||||||
|
|
||||||
|
// Add y-axis labels
|
||||||
|
for i := 0; i < m.height; i++ {
|
||||||
|
// Calculate the corresponding value for the y-axis
|
||||||
|
value := minValue + (maxValue-minValue)*float64(m.height-1-i)/float64(m.height-1)
|
||||||
|
// Format the value to 2 decimal places
|
||||||
|
label := fmt.Sprintf("%.2f", value)
|
||||||
|
// Add padding to align the labels
|
||||||
|
label = fmt.Sprintf("%6s", label)
|
||||||
|
plot += label + " "
|
||||||
|
// Add the grid row
|
||||||
|
for j := 0; j < m.width; j++ {
|
||||||
|
plot += grid[i][j]
|
||||||
|
}
|
||||||
|
plot += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add x-axis labels (time)
|
||||||
|
plot += " " // Align with y-axis labels
|
||||||
|
for i := 0; i < m.width; i += 10 {
|
||||||
|
// Calculate the time value for the x-axis
|
||||||
|
timeValue := m.time + float64(i)/10.0
|
||||||
|
// Format the time value to 1 decimal place
|
||||||
|
label := fmt.Sprintf("%.1f", timeValue)
|
||||||
|
label = fmt.Sprintf("%-10s", label)
|
||||||
|
plot += label
|
||||||
|
}
|
||||||
|
plot += "\n"
|
||||||
|
|
||||||
|
// Style the plot with a border
|
||||||
|
style := lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("#FFFFFF")). // White border
|
||||||
|
Padding(1, 2)
|
||||||
|
return style.Render(plot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Initialize the model
|
||||||
|
model := initialModel()
|
||||||
|
|
||||||
|
// Start the bubbletea program
|
||||||
|
p := tea.NewProgram(model, tea.WithAltScreen())
|
||||||
|
// _, err = p.Run()
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Printf("Error running program: %v\n", err)
|
||||||
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
p.Kill()
|
||||||
|
ports, err := enumerator.GetDetailedPortsList()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(ports) == 0 {
|
||||||
|
fmt.Println("No serial ports found!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, port := range ports {
|
||||||
|
fmt.Printf("Found port: %s\n", port.Name)
|
||||||
|
if port.IsUSB {
|
||||||
|
fmt.Printf(" USB ID %s:%s\n", port.VID, port.PID)
|
||||||
|
fmt.Printf(" USB serial %s\n", port.SerialNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
go.mod
Normal file
29
go.mod
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module git.belvedersky.ru/plotter
|
||||||
|
|
||||||
|
go 1.23.5
|
||||||
|
|
||||||
|
require github.com/charmbracelet/lipgloss v1.0.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/creack/goselect v0.1.2 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
golang.org/x/sync v0.9.0 // indirect
|
||||||
|
golang.org/x/text v0.3.8 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/bubbletea v1.2.4
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
go.bug.st/serial v1.6.2
|
||||||
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
)
|
45
go.sum
Normal file
45
go.sum
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
||||||
|
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
|
||||||
|
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
||||||
|
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||||
|
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||||
|
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||||
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
Loading…
x
Reference in New Issue
Block a user