init commit

This commit is contained in:
Кобелев Андрей Андреевич 2025-01-25 23:32:33 +03:00
commit a00e135687
4 changed files with 285 additions and 0 deletions

14
README.md Normal file
View 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
View 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
View 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
View 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=