589 lines
27 KiB
Go
589 lines
27 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"shtrih-kkt/pkg/shtrih"
|
||
|
||
"gopkg.in/natefinch/lumberjack.v2"
|
||
)
|
||
|
||
const (
|
||
configFileName = "connect.json"
|
||
serviceConfigName = "service.json"
|
||
logsDir = "logs"
|
||
comSearchTimeout = 200 * time.Millisecond
|
||
tcpSearchTimeout = 200 * time.Millisecond
|
||
)
|
||
|
||
// Глобальная переменная для пути вывода. Это позволяет подменять ее в тестах.
|
||
var (
|
||
outputDir = "date"
|
||
version = "0.1.5-dev"
|
||
)
|
||
|
||
// --- СТРУКТУРЫ ДЛЯ ПАРСИНГА КОНФИГУРАЦИОННЫХ ФАЙЛОВ ---
|
||
|
||
// ConfigFile используется для чтения секции "shtrih" из connect.json.
|
||
// Остальные секции файла игнорируются при чтении, но сохраняются при записи.
|
||
type ConfigFile struct {
|
||
// Если ключ "shtrih" в JSON отсутствует, это поле будет nil.
|
||
// Если ключ есть, но массив пуст (shtrih: []), поле будет пустым срезом.
|
||
Shtrih []ConnectionSettings `json:"shtrih"`
|
||
}
|
||
|
||
type ConnectionSettings struct {
|
||
TypeConnect int32 `json:"type_connect"`
|
||
ComPort string `json:"com_port"`
|
||
ComBaudrate string `json:"com_baudrate"`
|
||
IP string `json:"ip"`
|
||
IPPort string `json:"ip_port"`
|
||
}
|
||
|
||
type ServiceFile struct {
|
||
Service ServiceConfig `json:"service"`
|
||
}
|
||
|
||
type ServiceConfig struct {
|
||
LogLevel string `json:"log_level"`
|
||
LogDays int `json:"log_days"`
|
||
UpdateURL string `json:"update_url"`
|
||
}
|
||
|
||
type PolledDevice struct {
|
||
Config shtrih.Config
|
||
Info *shtrih.FiscalInfo
|
||
}
|
||
|
||
// --- ОСНОВНАЯ ЛОГИКА ПРИЛОЖЕНИЯ ---
|
||
|
||
func main() {
|
||
log.Printf("Запуск приложения для сбора данных с ККТ Штрих-М, версия: %s", version)
|
||
|
||
// Загружаем сервисную конфигурацию в самом начале.
|
||
serviceConfig := loadServiceConfig()
|
||
|
||
// Настраиваем файловое логирование.
|
||
setupLogger(serviceConfig)
|
||
|
||
// В фоне запускаем проверку обновлений, если URL указан.
|
||
if serviceConfig != nil {
|
||
go checkForUpdates(version, serviceConfig.UpdateURL)
|
||
}
|
||
|
||
// Основная логика приложения.
|
||
configData, err := os.ReadFile(configFileName)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
log.Printf("Файл конфигурации '%s' не найден. Запускаю режим автопоиска...", configFileName)
|
||
runDiscoveryMode()
|
||
} else {
|
||
log.Fatalf("Ошибка чтения файла конфигурации '%s': %v", configFileName, err)
|
||
}
|
||
} else {
|
||
log.Printf("Найден файл конфигурации '%s'. Запускаю стационарный режим...", configFileName)
|
||
runConfigMode(configData)
|
||
}
|
||
|
||
log.Println("Работа приложения завершена.")
|
||
}
|
||
|
||
// loadServiceConfig читает и парсит service.json.
|
||
// Возвращает конфигурацию или nil, если файл не найден или поврежден.
|
||
func loadServiceConfig() *ServiceConfig {
|
||
data, err := os.ReadFile(serviceConfigName)
|
||
if err != nil {
|
||
if !os.IsNotExist(err) {
|
||
log.Printf("Предупреждение: ошибка чтения файла '%s': %v.", serviceConfigName, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
var serviceFile ServiceFile
|
||
if err := json.Unmarshal(data, &serviceFile); err != nil {
|
||
log.Printf("Предупреждение: не удалось прочитать настройки из '%s' (%v).", serviceConfigName, err)
|
||
return nil
|
||
}
|
||
return &serviceFile.Service
|
||
}
|
||
|
||
func setupLogger(config *ServiceConfig) {
|
||
// setupLogger настраивает систему логирования на основе конфигурации.
|
||
// Если конфигурация не передана или повреждена, логирование продолжается в консоль.
|
||
// Создает директорию для логов и настраивает ротацию с использованием lumberjack.
|
||
if config == nil {
|
||
log.Printf("Предупреждение: файл настроек '%s' не найден или некорректен. Логирование продолжится в консоль.", serviceConfigName)
|
||
return
|
||
}
|
||
|
||
logDays := config.LogDays
|
||
if logDays <= 0 {
|
||
logDays = 7 // Значение по умолчанию
|
||
}
|
||
|
||
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||
log.Printf("Ошибка создания директории для логов '%s': %v. Логирование продолжится в консоль.", logsDir, err)
|
||
return
|
||
}
|
||
|
||
logFilePath := filepath.Join(logsDir, "shtrih-scanner.log")
|
||
|
||
lumberjackLogger := &lumberjack.Logger{
|
||
Filename: logFilePath,
|
||
MaxSize: 5,
|
||
MaxBackups: 10,
|
||
MaxAge: logDays,
|
||
Compress: true,
|
||
}
|
||
|
||
log.SetOutput(io.MultiWriter(os.Stdout, lumberjackLogger))
|
||
log.Printf("Логирование настроено. Уровень: %s, ротация: %d дней. Файл: %s", config.LogLevel, logDays, logFilePath)
|
||
}
|
||
|
||
func runConfigMode(data []byte) {
|
||
// runConfigMode запускает приложение в стационарном режиме с использованием
|
||
// конфигурации из файла connect.json. Парсит настройки устройств и запускает
|
||
// процесс опроса ККТ. При ошибках парсинга переключается на режим автопоиска.
|
||
|
||
var configFile ConfigFile
|
||
if err := json.Unmarshal(data, &configFile); err != nil {
|
||
log.Printf("Ошибка парсинга JSON из '%s': %v. Переключаюсь на режим автопоиска.", configFileName, err)
|
||
runDiscoveryMode()
|
||
return
|
||
}
|
||
|
||
// Проверяем наличие секции shtrih в файле, но не пустоту массива.
|
||
// Пустой массив shtrih: [] является валидным состоянием.
|
||
if configFile.Shtrih == nil {
|
||
log.Printf("В файле '%s' отсутствует секция 'shtrih'. Переключаюсь на режим автопоиска.", configFileName)
|
||
runDiscoveryMode()
|
||
return
|
||
}
|
||
|
||
if len(configFile.Shtrih) == 0 {
|
||
log.Println("Список устройств 'shtrih' в конфигурации пуст. Сканирование не требуется.")
|
||
// Здесь можно завершить работу, так как опрашивать нечего.
|
||
return
|
||
}
|
||
|
||
log.Printf("Найдено %d конфигураций для Штрих-М. Начинаю опрос...", len(configFile.Shtrih))
|
||
configs := convertSettingsToConfigs(configFile.Shtrih)
|
||
if len(configs) == 0 {
|
||
log.Println("Не удалось создать ни одной валидной конфигурации из файла. Проверьте данные в connect.json.")
|
||
return
|
||
}
|
||
|
||
// Передаем конструктор реального драйвера shtrih.New
|
||
processDevices(configs, shtrih.New)
|
||
}
|
||
|
||
func runDiscoveryMode() {
|
||
// runDiscoveryMode запускает приложение в режиме автопоиска устройств.
|
||
// Выполняет сканирование COM-портов и TCP-сетей для обнаружения ККТ Штрих-М.
|
||
// При обнаружении устройств сохраняет их конфигурацию для последующих запусков.
|
||
configs, err := shtrih.SearchDevices(comSearchTimeout, tcpSearchTimeout)
|
||
if err != nil {
|
||
log.Printf("Во время поиска устройств произошла ошибка: %v", err)
|
||
}
|
||
|
||
if len(configs) == 0 {
|
||
log.Println("В ходе сканирования не найдено ни одного устройства Штрих-М.")
|
||
// Сохраняем информацию об отсутствии устройств, чтобы не сканировать в следующий раз.
|
||
saveEmptyShtrihConfig()
|
||
return // Завершаем работу, так как устройств нет.
|
||
}
|
||
|
||
log.Printf("Найдено %d устройств. Начинаю сбор информации...", len(configs))
|
||
// Передаем конструктор реального драйвера shtrih.New
|
||
polledDevices := processDevices(configs, shtrih.New)
|
||
|
||
if len(polledDevices) > 0 {
|
||
saveConfiguration(polledDevices)
|
||
}
|
||
}
|
||
|
||
// processDevices принимает функцию-фабрику `newDriverFunc` для создания драйвера.
|
||
// Это позволяет подменять реальный драйвер на мок-драйвер в тестах.
|
||
func processDevices(configs []shtrih.Config, newDriverFunc func(shtrih.Config) shtrih.Driver) []PolledDevice {
|
||
var polledDevices []PolledDevice
|
||
for _, config := range configs {
|
||
log.Printf("--- Опрашиваю устройство: %+v ---", config)
|
||
// Используем переданную функцию-фабрику для создания драйвера
|
||
driver := newDriverFunc(config)
|
||
|
||
if err := driver.Connect(); err != nil {
|
||
log.Printf("Не удалось подключиться к устройству: %v", err)
|
||
continue
|
||
}
|
||
|
||
info, err := driver.GetFiscalInfo()
|
||
driver.Disconnect()
|
||
|
||
if err != nil {
|
||
log.Printf("Ошибка при получении фискальной информации: %v", err)
|
||
continue
|
||
}
|
||
if info == nil || info.SerialNumber == "" {
|
||
log.Println("Получена пустая информация или отсутствует серийный номер, данные проигнорированы.")
|
||
continue
|
||
}
|
||
polledDevices = append(polledDevices, PolledDevice{Config: config, Info: info})
|
||
}
|
||
|
||
if len(polledDevices) == 0 {
|
||
log.Println("--- Не удалось собрать данные ни с одного устройства. Завершение. ---")
|
||
return nil
|
||
}
|
||
|
||
log.Printf("--- Всего собрано данных с %d устройств. Начинаю обработку файлов. ---", len(polledDevices))
|
||
|
||
sourceWSDataMap := findSourceWorkstationData()
|
||
|
||
cleanupDateDirectory()
|
||
|
||
var successCount int
|
||
for _, pd := range polledDevices {
|
||
kktInfo := pd.Info
|
||
fileName := fmt.Sprintf("%s.json", kktInfo.SerialNumber)
|
||
filePath := filepath.Join(outputDir, fileName)
|
||
|
||
// Определяем, какие данные о рабочей станции использовать.
|
||
var wsDataToUse map[string]interface{}
|
||
if sourceWSDataMap != nil {
|
||
log.Printf("Готовлю данные для ККТ %s, используя информацию из файла-донора.", kktInfo.SerialNumber)
|
||
wsDataToUse = sourceWSDataMap
|
||
} else {
|
||
log.Printf("Готовлю данные для ККТ %s с базовой информацией о рабочей станции (донор не найден).", kktInfo.SerialNumber)
|
||
hostname, _ := os.Hostname()
|
||
wsDataToUse = map[string]interface{}{"hostname": hostname}
|
||
}
|
||
|
||
// Безусловно сохраняем/перезаписываем файл.
|
||
if err := saveNewMergedInfo(kktInfo, wsDataToUse, filePath); err != nil {
|
||
log.Printf("Не удалось создать/перезаписать файл для ККТ %s: %v", kktInfo.SerialNumber, err)
|
||
} else {
|
||
// Логика в saveNewMergedInfo уже выводит сообщение об успехе.
|
||
successCount++
|
||
}
|
||
}
|
||
log.Printf("--- Обработка файлов завершена. Успешно создано/обновлено: %d файлов. ---", successCount)
|
||
|
||
return polledDevices
|
||
}
|
||
|
||
// --- ФУНКЦИИ ДЛЯ РАБОТЫ С ФАЙЛАМИ ---
|
||
|
||
// findSourceWorkstationData ищет в папке /date файл с данными о рабочей станции.
|
||
// Логика поиска:
|
||
// 1. Ищет "идеальный" донор: файл с "hostname", но без "modelName". Если находит - сразу возвращает его.
|
||
// 2. Если идеальный не найден, ищет "первый подходящий": любой файл с "hostname", даже если там есть "modelName".
|
||
func findSourceWorkstationData() map[string]interface{} {
|
||
files, err := os.ReadDir(outputDir)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
var firstCandidate map[string]interface{} // Переменная для хранения "первого подходящего" кандидата
|
||
|
||
for _, file := range files {
|
||
if file.IsDir() || filepath.Ext(file.Name()) != ".json" {
|
||
continue
|
||
}
|
||
|
||
filePath := filepath.Join(outputDir, file.Name())
|
||
data, err := os.ReadFile(filePath)
|
||
if err != nil {
|
||
log.Printf("Предупреждение: не удалось прочитать файл-донор %s: %v", filePath, err)
|
||
continue
|
||
}
|
||
|
||
var content map[string]interface{}
|
||
if err := json.Unmarshal(data, &content); err != nil {
|
||
log.Printf("Предупреждение: не удалось распарсить JSON из файла-донора %s: %v", filePath, err)
|
||
continue
|
||
}
|
||
|
||
// Проверяем наличие ключевых полей
|
||
_, hasModelName := content["modelName"]
|
||
_, hasHostname := content["hostname"]
|
||
|
||
// Если у файла нет hostname, он нам точно не интересен
|
||
if !hasHostname {
|
||
continue
|
||
}
|
||
|
||
// Сценарий 1: Найден "идеальный" донор (без modelName)
|
||
if !hasModelName {
|
||
log.Printf("Найден идеальный файл-донор с данными о рабочей станции: %s", filePath)
|
||
return content // Сразу возвращаем его
|
||
}
|
||
|
||
// Сценарий 2: Файл не идеальный, но подходит как кандидат (есть и hostname, и modelName)
|
||
// Сохраняем только самого первого кандидата из списка файлов.
|
||
if firstCandidate == nil {
|
||
firstCandidate = content
|
||
log.Printf("Найден файл-кандидат на роль донора (будет использован, если не найдется идеальный): %s", filePath)
|
||
}
|
||
}
|
||
|
||
// После проверки всех файлов, если мы так и не вернули идеального донора,
|
||
// используем первого подходящего кандидата, которого нашли.
|
||
if firstCandidate != nil {
|
||
log.Println("Идеальный донор не найден, используется первый подходящий файл-кандидат.")
|
||
return firstCandidate
|
||
}
|
||
|
||
// Если мы дошли до сюда, значит не было найдено ни одного файла с полем "hostname".
|
||
log.Println("В папке /date не найдено файлов-доноров. Будут использованы базовые данные.")
|
||
return nil
|
||
}
|
||
|
||
// cleanupDateDirectory сканирует рабочую директорию и удаляет файлы,
|
||
// имя которых (без расширения) содержит нечисловые символы.
|
||
// Это необходимо для очистки временных/донорских файлов перед записью актуальных данных.
|
||
func cleanupDateDirectory() {
|
||
log.Println("Запуск очистки рабочей директории от временных файлов...")
|
||
|
||
files, err := os.ReadDir(outputDir)
|
||
if err != nil {
|
||
// Если директория еще не создана, это не ошибка. Просто выходим.
|
||
if os.IsNotExist(err) {
|
||
log.Printf("Директория '%s' не найдена, очистка не требуется.", outputDir)
|
||
return
|
||
}
|
||
log.Printf("Ошибка чтения директории '%s' при очистке: %v", outputDir, err)
|
||
return
|
||
}
|
||
|
||
// Регулярное выражение для проверки, что строка состоит только из цифр.
|
||
isNumeric := regexp.MustCompile(`^[0-9]+$`).MatchString
|
||
deletedCount := 0
|
||
|
||
for _, file := range files {
|
||
if file.IsDir() || filepath.Ext(file.Name()) != ".json" {
|
||
continue
|
||
}
|
||
|
||
// Получаем имя файла без расширения .json
|
||
baseName := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||
|
||
if !isNumeric(baseName) {
|
||
filePath := filepath.Join(outputDir, file.Name())
|
||
log.Printf("Обнаружен некорректный файл '%s'. Удаляю...", file.Name())
|
||
if err := os.Remove(filePath); err != nil {
|
||
log.Printf("Не удалось удалить файл '%s': %v", filePath, err)
|
||
} else {
|
||
log.Printf("Файл '%s' успешно удален.", file.Name())
|
||
deletedCount++
|
||
}
|
||
}
|
||
}
|
||
|
||
if deletedCount > 0 {
|
||
log.Printf("Очистка завершена. Удалено %d файлов.", deletedCount)
|
||
} else {
|
||
log.Println("Некорректных файлов для удаления не найдено.")
|
||
}
|
||
}
|
||
|
||
// saveNewMergedInfo объединяет данные ККТ и данные рабочей станции (в виде map) и сохраняет в новый JSON-файл.
|
||
// Данные от ККТ имеют приоритет и перезаписывают одноименные поля из данных донора.
|
||
func saveNewMergedInfo(kktInfo *shtrih.FiscalInfo, wsData map[string]interface{}, filePath string) error {
|
||
// Шаг 1: Преобразуем данные от нашего ККТ (Штрих) в map.
|
||
var kktMap map[string]interface{}
|
||
kktJSON, _ := json.Marshal(kktInfo)
|
||
json.Unmarshal(kktJSON, &kktMap)
|
||
|
||
// Шаг 2: Создаем итоговую карту. Начинаем с данных донора, чтобы они были "внизу".
|
||
// Мы делаем копию wsData, чтобы не изменять оригинальную карту, которая может быть использована в других итерациях.
|
||
finalMap := make(map[string]interface{})
|
||
for key, value := range wsData {
|
||
finalMap[key] = value
|
||
}
|
||
|
||
// Шаг 3: "Накладываем" данные от нашего ККТ поверх.
|
||
// Все совпадающие ключи будут перезаписаны значениями от Штриха.
|
||
for key, value := range kktMap {
|
||
// Пропускаем пустые значения от ККТ, чтобы случайно не затереть
|
||
// хорошее значение из донора пустым.
|
||
if s, ok := value.(string); ok && s == "" {
|
||
continue
|
||
}
|
||
finalMap[key] = value
|
||
}
|
||
|
||
// Шаг 4: Устанавливаем актуальные временные метки. Они всегда должны быть свежими.
|
||
currentTime := time.Now().Format("2006-01-02 15:04:05")
|
||
finalMap["current_time"] = currentTime
|
||
finalMap["v_time"] = currentTime
|
||
|
||
// Шаг 5: Создаем директорию и сохраняем файл.
|
||
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
|
||
return fmt.Errorf("не удалось создать директорию '%s': %w", outputDir, err)
|
||
}
|
||
|
||
finalJSON, err := json.MarshalIndent(finalMap, "", " ")
|
||
if err != nil {
|
||
return fmt.Errorf("ошибка маршалинга итогового JSON: %w", err)
|
||
}
|
||
|
||
if err := os.WriteFile(filePath, finalJSON, 0644); err != nil {
|
||
return fmt.Errorf("ошибка записи в файл '%s': %w", filePath, err)
|
||
}
|
||
|
||
log.Printf("Данные для ККТ %s успешно сохранены в новый файл: %s", kktInfo.SerialNumber, filePath)
|
||
return nil
|
||
}
|
||
|
||
// saveEmptyShtrihConfig создает или обновляет connect.json, указывая,
|
||
// что устройства "Штрих-М" не были найдены. Функция работает неразрушающим
|
||
// образом, сохраняя все остальные данные в файле.
|
||
func saveEmptyShtrihConfig() {
|
||
log.Printf("Сохраняю конфигурацию с пустым списком устройств Штрих-М в '%s'...", configFileName)
|
||
|
||
// Используем map[string]interface{} для редактирования JSON.
|
||
configMap := make(map[string]interface{})
|
||
|
||
// Пытаемся прочитать существующий файл, чтобы не затереть другие секции.
|
||
data, err := os.ReadFile(configFileName)
|
||
if err == nil {
|
||
// Если файл есть, парсим его в нашу карту.
|
||
if err := json.Unmarshal(data, &configMap); err != nil {
|
||
log.Printf("Предупреждение: файл '%s' поврежден (%v). Он будет перезаписан.", configFileName, err)
|
||
// В случае ошибки парсинга, начинаем с пустой карты, чтобы исправить файл.
|
||
configMap = make(map[string]interface{})
|
||
}
|
||
} else if !os.IsNotExist(err) {
|
||
// Логируем ошибку, если это не "файл не найден".
|
||
log.Printf("Предупреждение: не удалось прочитать '%s' (%v). Файл будет создан заново.", configFileName, err)
|
||
}
|
||
|
||
// Устанавливаем или обновляем только ключ 'shtrih'.
|
||
configMap["shtrih"] = []ConnectionSettings{}
|
||
|
||
// Маршалинг и запись обратно в файл.
|
||
updatedData, err := json.MarshalIndent(configMap, "", " ")
|
||
if err != nil {
|
||
log.Printf("Ошибка: не удалось преобразовать пустую конфигурацию в JSON: %v", err)
|
||
return
|
||
}
|
||
if err := os.WriteFile(configFileName, updatedData, 0644); err != nil {
|
||
log.Printf("Ошибка: не удалось записать пустую конфигурацию в файл '%s': %v", configFileName, err)
|
||
return
|
||
}
|
||
log.Printf("Файл '%s' успешно обновлен с отметкой об отсутствии устройств Штрих-М.", configFileName)
|
||
}
|
||
|
||
func saveConfiguration(polledDevices []PolledDevice) {
|
||
// saveConfiguration сохраняет конфигурацию найденных устройств в файл connect.json.
|
||
// Функция работает неразрушающим образом, сохраняя все остальные секции файла.
|
||
// Преобразует внутренние структуры shtrih.Config в формат ConnectionSettings для JSON.
|
||
log.Printf("Сохранение %d найденных конфигураций в файл '%s'...", len(polledDevices), configFileName)
|
||
|
||
// Используем map[string]interface{} для неразрушающего редактирования JSON.
|
||
configMap := make(map[string]interface{})
|
||
|
||
// Пытаемся прочитать существующий файл, чтобы не затереть другие секции.
|
||
data, err := os.ReadFile(configFileName)
|
||
if err == nil {
|
||
// Если файл есть, парсим его в нашу карту.
|
||
if err := json.Unmarshal(data, &configMap); err != nil {
|
||
log.Printf("Предупреждение: файл '%s' поврежден (%v). Он будет перезаписан.", configFileName, err)
|
||
// В случае ошибки парсинга, начинаем с пустой карты.
|
||
configMap = make(map[string]interface{})
|
||
}
|
||
} else if !os.IsNotExist(err) {
|
||
log.Printf("Предупреждение: не удалось прочитать '%s' (%v). Файл будет создан заново.", configFileName, err)
|
||
}
|
||
|
||
// Готовим новый срез с настройками для устройств Штрих-М.
|
||
var newShtrihSettings []ConnectionSettings
|
||
for _, pd := range polledDevices {
|
||
newShtrihSettings = append(newShtrihSettings, convertConfigToSettings(pd.Config))
|
||
}
|
||
|
||
// Обновляем в карте только ключ 'shtrih'. Все остальные ключи остаются нетронутыми.
|
||
configMap["shtrih"] = newShtrihSettings
|
||
|
||
// Маршалинг и запись обратно в файл.
|
||
updatedData, err := json.MarshalIndent(configMap, "", " ")
|
||
if err != nil {
|
||
log.Printf("Ошибка: не удалось преобразовать конфигурацию в JSON: %v", err)
|
||
return
|
||
}
|
||
if err := os.WriteFile(configFileName, updatedData, 0644); err != nil {
|
||
log.Printf("Ошибка: не удалось записать конфигурацию в файл '%s': %v", configFileName, err)
|
||
return
|
||
}
|
||
log.Printf("Конфигурация успешно сохранена в '%s'.", configFileName)
|
||
}
|
||
|
||
// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
|
||
|
||
func convertSettingsToConfigs(settings []ConnectionSettings) []shtrih.Config {
|
||
// convertSettingsToConfigs преобразует настройки подключения из JSON-формата
|
||
// в внутренние структуры shtrih.Config. Выполняет валидацию параметров
|
||
// и пропускает некорректные конфигурации с соответствующим логированием.
|
||
var configs []shtrih.Config
|
||
baudRateMap := map[string]int32{
|
||
"115200": 6, "57600": 5, "38400": 4, "19200": 3, "9600": 2, "4800": 1,
|
||
}
|
||
for _, s := range settings {
|
||
config := shtrih.Config{ConnectionType: s.TypeConnect, Password: 30}
|
||
switch s.TypeConnect {
|
||
case 0:
|
||
comNum, err := strconv.Atoi(s.ComPort[3:])
|
||
if err != nil {
|
||
log.Printf("Некорректное имя COM-порта '%s' в конфигурации, пропуск.", s.ComPort)
|
||
continue
|
||
}
|
||
baudRate, ok := baudRateMap[s.ComBaudrate]
|
||
if !ok {
|
||
log.Printf("Некорректная скорость '%s' для порта '%s', пропуск.", s.ComBaudrate, s.ComPort)
|
||
continue
|
||
}
|
||
config.ComName = s.ComPort
|
||
config.ComNumber = int32(comNum)
|
||
config.BaudRate = baudRate
|
||
case 6:
|
||
port, err := strconv.Atoi(s.IPPort)
|
||
if err != nil {
|
||
log.Printf("Некорректный TCP-порт '%s' для IP '%s', пропуск.", s.IPPort, s.IP)
|
||
continue
|
||
}
|
||
config.IPAddress = s.IP
|
||
config.TCPPort = int32(port)
|
||
default:
|
||
log.Printf("Неизвестный тип подключения '%d', пропуск.", s.TypeConnect)
|
||
continue
|
||
}
|
||
configs = append(configs, config)
|
||
}
|
||
return configs
|
||
}
|
||
|
||
func convertConfigToSettings(config shtrih.Config) ConnectionSettings {
|
||
baudRateReverseMap := map[int32]string{
|
||
6: "115200", 5: "57600", 4: "38400", 3: "19200", 2: "9600", 1: "4800",
|
||
}
|
||
settings := ConnectionSettings{TypeConnect: config.ConnectionType}
|
||
switch config.ConnectionType {
|
||
case 0:
|
||
settings.ComPort = config.ComName
|
||
settings.ComBaudrate = baudRateReverseMap[config.BaudRate]
|
||
case 6:
|
||
settings.IP = config.IPAddress
|
||
settings.IPPort = strconv.Itoa(int(config.TCPPort))
|
||
}
|
||
return settings
|
||
}
|