Files
my-vault/04_Archive/Projects/Airport/Guangzhou/New Telegram-MacBook Pro.md
T

18 KiB
Raw Blame History

技术选型

串口数据接收

  1. 串口模拟器
    1. socat
      1. Create Virtual Serial Port Pair Use socat to create two linked virtual serial ports. This example creates two virtual serial ports: /dev/ttyV0 and /dev/ttyV1.

socat -d -d PTY,link=./dev/ttyV0,raw,echo=0 PTY,link=./dev/ttyV1,raw,echo=0 **Explanation:** - `-d -d`: Enables debug output to see the details of the operations. - `PTY,link=/dev/ttyV0,raw,echo=0`: Creates a pseudo-terminal (PTY) with a symbolic link `/dev/ttyV0`, in raw mode with no echo. - `PTY,link=/dev/ttyV1,raw,echo=0`: Creates another PTY with a symbolic link `/dev/ttyV1`, in raw mode with no echo. - bash screen ./dev/ttyV0 9600

	  echo "hello" > ./dev/ttyV1
```

消息队列

nats-server

架构设计

micrioprofile openliberty

micronaut

  1. 串口读取
  2. 报文拆分
  3. 动态解析
  4. 计划解析
  5. 总线消息生成
  6. 动态信息转发
  7. 计划转发

串口 => 消息流原始 => 已分离电报消息 => 待发送 => 消息总线

系统总线适配

日计划处理
季度计划处理
航班主要信息同步
动态消息转发

串口转消息队列

命令行工具 1. 串口参数 2. 消息队列参数

电报存储

postgresql pocketbase

nats nui pocketbase meilisearch

compose.yml

services:
  nats:
    container_name: nats
    image: nats
    ports:
      - "4222:4222"
      - "6222:6222"
      - "8222:8222"
    restart: unless-stopped

  nui:
    container_name: nui
    image: ghcr.io/nats-nui/nui
    volumes:
      - ./db:/db
      - ./creds:/nats-creds:ro
    ports:
      - "31311:31311"
    restart: unless-stopped

  pocketbase:
    image: ghcr.io/muchobien/pocketbase:latest
    container_name: pocketbase
    restart: unless-stopped
    command:
      #- --encryptionEnv #optional
      #- ENCRYPTION #optional
    environment:
      ENCRYPTION: example #optional
    ports:
      - "8090:8090"
    volumes:
      - ./data:/pb_data
      - ./public:/pb_public #optional
      - ./hooks:/pb_hooks #optional

    healthcheck: #optional (recommended) since v0.10.0
      test: wget --no-verbose --tries=1 --spider http://localhost:8090/api/health || exit 1
      interval: 30s
      timeout: 5s
      retries: 5
  meilisearch:
    container_name: meilisearch
    image: getmeili/meilisearch:v1.8
    environment:
      - http_proxy
      - https_proxy
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:-masterKey}
      - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-true}
      - MEILI_ENV=${MEILI_ENV:-development}
    ports:
      - ${MEILI_PORT:-7700}:7700
    volumes:
      - ./meili_data:/meili_data
    restart: unless-stopped

nat with monitor:

networks:
  monitor-net:
    driver: bridge

services:
  nats:
    image: nats
    container_name: nats
    restart: always
    command: -c /etc/nats/nats.conf
    ports:
      - "4222:4222" # client port
      - "6222:6222" # cluster port
      - "8222:8222" # monitoring port
    volumes:
      - ./nats.conf:/etc/nats/nats.conf
      - $JETSTREAM_STORAGE:/data
    networks:
      - monitor-net

  exporter:
    image: natsio/prometheus-nats-exporter
    container_name: nats-exporter
    restart: always
    command: 
      # see https://github.com/nats-io/prometheus-nats-exporter/blob/main/main.go#L87
      # "-connz",               # connection metrics
      - -connz_detailed         # advanced connection metrics
      - -jsz
      - all                # jetstream metrics
      - -routez                 # route metrics
      - -subz                   # subscription metrics
      - -varz                   # general metrics
      - -prefix=nats            # prefix for all metrics
      - -use_internal_server_id # using serverID from /varz
      - http://nats:8222/
    networks:
      - monitor-net
    labels:
      org.label-schema.group: "nats-monitoring"
    depends_on:
      - nats

    # ports:
    #   - "7777:7777"
    
  surveyor:
    image: natsio/nats-surveyor
    container_name: nats-surveyor
    restart: always
    volumes:
      - ./observations:/observations
      - $JETSTREAM_STORAGE:/data
    command: |
      -s "${NATS_SURVEYOR_SERVERS}" --accounts --observe /observations --jetstream /data
    networks:
      - monitor-net
    labels:
      org.label-schema.group: "nats-monitoring"
    depends_on:
      - nats

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: always
    volumes:
      - ./prometheus/:/etc/prometheus/
      - $PROMETHEUS_STORAGE:/usr/local/share/prometheus
    command: --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/usr/local/share/prometheus
    networks:
      - monitor-net
    labels:
      org.label-schema.group: "nats-monitoring"
    ports:
      - "9090:9090"
    depends_on:
      - surveyor
      - exporter

  grafana:
    image: grafana/grafana
    container_name: grafana
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/dashboards:/var/lib/grafana/dashboards
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - monitor-net
    labels:
      org.label-schema.group: "nats-monitoring"
    depends_on:
      - prometheus

电报解析,电报存储,电报转发

管理界面接口

管理界面

电报界面 日计划界面 全文检索 typesense meli

nextjs shadcn svelte shadcn melt-ui: https://melt-ui.com flowbite: Flowbite Svelte

htmx: go: chi htmx tailwindcss echo htmx tailwindcss python:

deno: fresh

Telegram Message Body Expression:

FPL:


\((?P<category>[A-Z]{3})-(?P<number>[A-Z]+\d+)-(?P<indicator>[A-Z]{2})\n-(?P<aircraft>[A-Z]+\d+\/?[A-Z]?)\n?-(?P<surve>.*)\n?-(?P<departure>[A-Z]{4})(?P<departure_time>\d{4})\n?-(?P<speed>[A-Z]+\d+)(?P<level>[A-Z0-9]+)\s+(?P<route>(.|\n)+)\n-(?P<destination>[A-Z]{4})(?P<estt>\d{4})\s+(?P<alter>[A-Z]{4})\n?-([A-Z]{3}\/(?:[A-Z]{4}\d{4}\s?)+)?(?P<other>(?m)[A-Z]{3}\/(.|\n)*)\)$

ARR:


\\((?P<type>[A-Z]{3})\\-(?P<number>([A-Z]+\\d+))\\/?(?P<ssr>[A-Z]+\\d+)\\-(?P<departure>[A-Z]{4})\\-(?P<arrival>[A-Z]{4})(?P<time>\\d{4})\\)

DEP:


^\\((?P<type>[A-Z]{3})\\-(?P<number>([A-Z]+\\d+))\\/?(?P<ssr>[A-Z]+\\d+)\\-(?P<departure>[A-Z]{4})(?P<departure_time>\\d{4})\\-(?P<destination>[A-Z]{4})(?P<destination_time>\\d{4})?\\-?(?P<alter>[A-Z]{4})?\\-?(?P<other>.*)?\\)$

NEW FPL:

\((?P<category>[A-Z]{3})-(?P<number>[A-Z]+\d+)-(?P<indicator>[A-Z]{2})\n-(?P<aircraft>[A-Z]+\d+\/?[A-Z]?)\n?-(?P<surve>.*)\n?-(?P<departure>[A-Z]{4})(?P<departure_time>\d{4})\n?-(?P<speed>[A-Z]+\d+)(?P<level>[A-Z0-9]+)\s+(?P<route>(.|\n)+)\n-(?P<destination>[A-Z]{4})(?P<estt>\d{4})\s?(?P<alter>(\s[A-Z]{4})+)\n?-([A-Z]{3}\/(?:[A-Z]{4}\d{4}\s?)+)?(?P<other>(?m)[A-Z]{3}\/(.|\n)*)\)$

CNL:

^\((?P<category>[A-Z]{3})-(?P<number>\w+\d+)-?(?P<departure>[A-Z]{4})?-?(?<destination>[A-Z]{4})\)$

DLA:

^\((?P<category>[A-Z]{3})-(?P<number>\w+\d+)-?(?P<departure>[A-Z]{4})(?P<departure_time>\d{4})?-?(?<destination>[A-Z]{4})(?<destination_time>\d{4})?\)$
ZCZC TMQ1067 150518     


QU TSNZPCA

.     

QU SZXZPCA TAOZPCA WNZZPCA SHATZCR PEKZPCA WUHZPCA TSNZPCA

.TNAUOSC 150519

PLN 16MAY

01)  SC4731/4732/4667/4668 B2968/B733 ILS I (7) TAO/2340 WNZ/0225

 SZX/

    0500 WNZ/0725 TAO/1000 SHA/1210

      SI:AWY/TAO WNZ SZX WNZ TAO SHA TAO

02)  SC4617/4618/4655/4656/4087/4088/4669/4670 B2996/B733 ILS I (8)

 TA

    O/0030 WUH/0255 TAO/0520 PEK/0730 TAO/0945 ICN/1155 TAO/1350

 SHA/1550

      SI:AWY/TAO WUH TAO PEK TAO ICN TAO SHA TAO

03)  SC4621/4622/4733/4734 B3005/CRJ2 ILS I (5) TAO/0005 DLC/0130

 TAO/

    0300 XIY/0545 KWE/0745 XIY/0950

      SI:AWY/TAO DLC TAO XIY KWE XIY TAO

04)  SC4607/4608/4623/4880/4879/4624 B3007/CRJ2 ILS I (5) TAO/2355

 TY

    N/0150 ZGC/0350 TYN/0545 TAO/0745 DLC/0900 YNT/1015 HGH/1225

 YNT/1435 DLC

    /1550

      SI:AWY/TAO TYN ZGC TYN TAO DLC YNT HGH YNT DLC TAO

05)  SC4679/4680/4711/4712 B3079/CRJ7 ILS I (5) TAO/0001 HGH/0210

 FOC/

    0350 HGH/0530 TAO/0735 HGH/0940 NNG/1255 HGH/1525

      SI:AWY/TAO HGH FOC HGH TAO HGH NNG HGH TAO

06)  SC4717/4817/4818/4718 B3080/CRJ7 ILS I (5) TAO/0005 TSN/0135

 HET/

    0320 TGO/0535 HET/0750 TSN/0930

      SI:AWY/TAO TSN HET TGO HET TSN TAO

07)  SC4709/4981/4982 B5065/B733 ILS I (7) TAO/2355 NGB/0155

 XMN/0430 

    TNA/0725 HET/0950 TNA/1200

      SI:AWY/TAO NGB XMN TNA HET TNA XMN

08)  SC4651/1152/1155/1156/1165/1166/1167/1168 B5205/B737 ILS I (7)

 TA

    O/2340 PEK/0140 TNA/0320 PEK/0510 TNA/0715 SHA/0925 TNA/1135

 SHA/1340

      SI:AWY/TAO PEK TNA PEK TNA SHA TNA SHA TNA

09)  SC4695/4696/4657/4658 B5331/B738 ILS I (8) TAO/2350 NKG/0145

 KMG/

    0505 NKG/0830 TAO/1045 PEK/1300

      SI:AWY/TAO NKG KMG NKG TAO PEK TAO

10)  SC4601/4602/4675/4672 B5348/B738 ILS I (10) TAO/2330 TNA/0055

 CTU

    /0405 TNA/0645 TAO/0830 CAN/1225 LYI/1515

      SI:AWY/TAO TNA CTU TNA TAO CAN LYI TAO

11)  SC4705/4706/4751/4752 B5349/B738 ILS I (8) TAO/2340 HFE/0135

 HHA/

    0335 HFE/0530 TAO/0720 CGO/0930 KWL/1210 CGO/1440

      SI:AWY/TAO HFE HHA HFE TAO CGO KWL CGO TAO

12)  SC4671/4676/4665/4666/4659/4660 B5350/B738 ILS I (8) TAO/2350

 LYI

    /0105 CAN/0415 TAO/0745 SHA/0950 TAO/1155 PEK/1400

      SI:AWY/TAO LYI CAN TAO SHA TAO 

PART ONE  CONTINUED

=




NNNN

ZCZC TMQ2544 141652


FF ZBTJZXZX


141652 ZBTJZPZX


(FPL-JAE7433-IS


-B744/H-SXIRPZJWY/S


-ZBTJ1755


-K0926S0920
 

-EDDF0948 EDDK


-EET/ZMUB0100 UNKL0236 UNWW0332 UNNT0332 USRR0447 USHH0507


 USSS0535 UUYY0602 ULKK0634 ULWW0653 ULLL0720 EETT0748 EVRR0815


 ESAA0821 EPWW0848 EDUU0900


 REG/B2422 SEL/JLAD OPR/JADE CARGO DAT/S RVR/200


 NAV/RNAV1 RNAV5 RNP4


 RMK/AGCS EQUIPPED


 ACARS EQUIPPED/TCAS EQUIPPED/FOREIGN PILOT


 E/1148 P/TBN R/UV S/M J/LF D/1 15 C YELLOW


 A/WHITE GREEN)

NNNN

计划:

airway

(?P<arr_time>\d{4}(\(\d{2}\w{3}\))?)(?P<airport>\w{3})\/?(?P<dep_time>\d{4}(\(\d{2}\w{3}\))?)

Store

nats: serial to nats

services:
  nats:
    container_name: nats
    image: nats
    ports:
      - "4222:4222"
      - "6222:6222"
      - "8222:8222"
    command: >
      -js
      -D
    environment:
      - NATS_SERVER_LOG_NAME=nats-server
    volumes:
      - ./nats_data:/data # Persistent storage for JetStream
    restart: unless-stopped

  nui:
    container_name: nui
    image: ghcr.io/nats-nui/nui
    volumes:
      - ./db:/db
      - ./creds:/nats-creds:ro
    ports:
      - "31311:31311"
    restart: unless-stopped

nats jetstream:

package main

  

import (

"fmt"

"log"

"time"

  

"github.com/nats-io/nats.go"

)

  

func publishMessage(js nats.JetStreamContext, subject string, message []byte) error {

ack, err := js.Publish(subject, message)

if err != nil {

return fmt.Errorf("error publishing message: %v", err)

}

  

log.Printf("Published message to %s, stream sequence: %d", subject, ack.Sequence)

return nil

}

  

func publishMessages(js nats.JetStreamContext, subject string, count int) error {

for i := 1; i <= count; i++ {

msg := []byte(fmt.Sprintf("Hello JetStream! Message #%d", i))

  

if err := publishMessage(js, subject, msg); err != nil {

return fmt.Errorf("failed to publish message %d: %v", i, err)

}

  

time.Sleep(1 * time.Second)

}

return nil

}

  

func setupStream(js nats.JetStreamContext, name string, subjects []string) error {

streamConfig := &nats.StreamConfig{

Name: name,

Subjects: subjects,

Storage: nats.MemoryStorage,

MaxAge: time.Hour,

}

  

// Try to create stream, if it already exists, update it

_, err := js.AddStream(streamConfig)

if err != nil {

if err == nats.ErrStreamNameAlreadyInUse {

_, err = js.UpdateStream(streamConfig)

if err != nil {

return fmt.Errorf("error updating stream: %v", err)

}

log.Printf("Updated stream %s", name)

return nil

}

return fmt.Errorf("error creating stream: %v", err)

}

  

log.Printf("Created stream %s", name)

return nil

}

  

func connectToNATS() (*nats.Conn, nats.JetStreamContext, error) {

opts := []nats.Option{

nats.Timeout(5 * time.Second),

nats.RetryOnFailedConnect(true),

nats.MaxReconnects(5),

nats.ReconnectWait(time.Second),

nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {

log.Printf("Disconnected due to: %s, will attempt reconnects for 5 times", err)

}),

nats.ReconnectHandler(func(nc *nats.Conn) {

log.Printf("Reconnected [%s]", nc.ConnectedUrl())

}),

nats.ErrorHandler(func(nc *nats.Conn, sub *nats.Subscription, err error) {

log.Printf("Error: %v", err)

}),

}

  

nc, err := nats.Connect("nats://localhost:4222", opts...)

if err != nil {

return nil, nil, fmt.Errorf("error connecting to NATS: %v", err)

}

  

js, err := nc.JetStream()

if err != nil {

nc.Close()

return nil, nil, fmt.Errorf("error getting JetStream context: %v", err)

}

  

// Setup the stream

if err := setupStream(js, "TEST_STREAM", []string{"test.>"}); err != nil {

nc.Close()

return nil, nil, err

}

  

return nc, js, nil

}

  

func subscribeToMessages(js nats.JetStreamContext, subject string, expectedMsgs int) (*nats.Subscription, chan bool, error) {

done := make(chan bool)

msgCount := 0

  

// Create a JetStream subscription

sub, err := js.Subscribe(subject, func(msg *nats.Msg) {

// Handle the message

log.Printf("Received message: %s\n", string(msg.Data))

log.Printf("Subject: %s\n", msg.Subject)

  

// Get metadata if available

if meta, err := msg.Metadata(); err == nil {

log.Printf("Sequence: %d\n", meta.Sequence.Stream)

log.Printf("Timestamp: %v\n", meta.Timestamp)

}

  

// Acknowledge the message

if err := msg.Ack(); err != nil {

log.Printf("Error acknowledging message: %v", err)

}

  

// Increment message count and check if we're done

msgCount++

if msgCount >= expectedMsgs {

done <- true

}

}, nats.DeliverAll(), nats.AckExplicit())

  

if err != nil {

return nil, nil, fmt.Errorf("error subscribing to subject %s: %v", subject, err)

}

  

log.Printf("Subscribed to %s", subject)

return sub, done, nil

}

  

func main() {

// Connect to NATS and get JetStream context

nc, js, err := connectToNATS()

if err != nil {

log.Fatalf("Failed to connect: %v", err)

}

defer nc.Close()

  

expectedMsgs := 5

  

// Subscribe to messages

sub, done, err := subscribeToMessages(js, "test.>", expectedMsgs)

if err != nil {

log.Fatalf("Failed to subscribe: %v", err)

}

defer sub.Unsubscribe()

  

// Publish messages

if err := publishMessages(js, "test.message", expectedMsgs); err != nil {

log.Printf("Error in publish sequence: %v", err)

}

  

// Wait for all messages to be received

log.Printf("Waiting for %d messages...", expectedMsgs)

<-done

log.Printf("All %d messages received. Exiting...", expectedMsgs)

}

nats with solidjs

main.ts:

import { Hono } from "jsr:@hono/hono";

import { connect, StringCodec } from "nats";

import process from "node:process";

import { upgradeWebSocket } from "jsr:@hono/hono/deno";

  

// Connect to NATS Server

let nc: Awaited<ReturnType<typeof connect>>;

try {

nc = await connect({ servers: "nats://localhost:4222" });

console.log("✅ Connected to NATS");

} catch (error) {

console.error("❌ Failed to connect to NATS:", error);

process.exit(1);

}

const sc = StringCodec();

  

// Store active WebSocket clients

const clients = new Set<WebSocket>();

  

// Create Hono app

const app = new Hono();

  

// WebSocket Upgrade Route

app.get("/ws", upgradeWebSocket((c) => {

return {

onMessage(event, ws) {

console.log(`📩 Received from client: ${event.data}`);

// Broadcast to other clients

for (const client of clients) {

if (client !== ws && client.readyState === WebSocket.OPEN) {

client.send(event.data);

}

}

// Publish to NATS

nc.publish("updates", sc.encode(event.data));

},

onClose() {

console.log("❌ WebSocket client disconnected");

},

onOpen(ws) {

clients.add(ws);

console.log("🔗 New WebSocket client connected");

}

};

}));

  

// Start WebSocket Server

console.log("🚀 Starting Hono WebSocket Server on http://localhost:3001/ws");

  

// Subscribe to NATS topic and forward messages to WebSockets

(async () => {

if (!nc) return;

const sub = nc.subscribe("updates");

for await (const msg of sub) {

const message = sc.decode(msg.data);

console.log(`📩 Received from NATS: ${message}`);

  

for (const client of clients) {

if (client.readyState === WebSocket.OPEN) {

client.send(message);

}

}

}

})();

  

// Start the server

await Deno.serve({ port: 3001 }, app.fetch);

websocketclient.tsx

import { createSignal, onCleanup, onMount } from "solid-js";

  

export default function WebSocketClient() {

const [messages, setMessages] = createSignal<string[]>([]);

const [input, setInput] = createSignal("");

let ws: WebSocket;

  

// Establish WebSocket connection

onMount(() => {

ws = new WebSocket("ws://localhost:3001/ws");

  

ws.onopen = () => console.log("✅ WebSocket connected");

ws.onmessage = (event) => {

console.log("Received message:", event.data);

setMessages((prev) => [...prev, event.data]);

};

  

ws.onclose = () => console.log("❌ WebSocket disconnected");

ws.onerror = (err) => console.error("❗ WebSocket error:", err);

  

// Cleanup WebSocket connection when component unmounts

onCleanup(() => ws.close());

});

  

// Send message via WebSocket (which then publishes to NATS)

const sendMessage = () => {

if (ws.readyState === WebSocket.OPEN && input().trim() !== "") {

ws.send(input());

setMessages((prev) => [...prev, input()]);

setInput(""); // Clear input after sending

}

};

  

return (

<div>

<h2>📡 NATS WebSocket Chat</h2>

  

<div>

<input

type="text"

placeholder="Type a message..."

value={input()}

onInput={(e) => setInput(e.currentTarget.value)}

/>

<button onClick={sendMessage}>Send</button>

</div>

  

<ul>

{messages().map((msg, index) => (

<li>

{index} {msg}

</li>

))}

</ul>

</div>

);

}