Przejdź do głównej zawartości

GPT Pipeline

CICD1

CICD2

CICD3

1. Architektura pipeline’u

Pipeline składa się z:

  • Linting (ruff)
  • Testy jednostkowe (pytest + stuby MicroPython)
  • Testy HIL (hardware-in-the-loop) przez mpremote
  • Budowanie paczki (opcjonalne .mpy, archiwum)
  • Deployment: USB + OTA dwa sloty
  • Release: tag, artefakty, rollback (opcjonalnie OTA0/OTA1)

2. Struktura projektu

project/

├── src/
│ ├── main.py
│ ├── config.py
│ ├── drivers/
│ │ ├── lis3dhtr.py
│ │ ├── oled_ssd1315.py
│ │ └── __init__.py
│ ├── services/
│ │ └── sensor_service.py
│ └── utils/
│ └── math_utils.py

├── tests/
│ ├── test_math_utils.py
│ ├── test_sensor_service.py
│ └── hw/
│ ├── test_i2c_scan.py
│ └── test_lis3dhtr_live.py

├── stubs/
│ └── micropython.pyi

├── scripts/
│ ├── deploy_usb.sh
│ ├── deploy_ota.py
│ ├── device_reset.sh
│ └── sync_to_board.sh

├── requirements.txt
├── ruff.toml
├── pytest.ini
├── Jenkinsfile
└── README.md

3. Konfiguracja ruff

line-length = 120
target-version = "py311"

[lint]
select = ["E", "F", "W", "I"]
ignore = ["E501"]

[mccabe]
max-complexity = 10

4. Testy jednostkowe – pytest

4.1 Przykład testu logiki

from src.utils.math_utils import clamp

def test_clamp():
assert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(20, 0, 10) == 10

4.2 Stub MicroPython

class Pin:
IN = 0
OUT = 1

def __init__(self, pin, mode=None):
self.pin = pin
self.mode = mode
self.state = 0

def value(self, v=None):
if v is None:
return self.state
self.state = v

5. Testy Hardware-in-the-loop (HIL)

import subprocess
import json

def test_i2c_scan_live():
result = subprocess.check_output(
["mpremote", "exec", "import ujson, machine; "
"i=machine.I2C(0, scl=machine.Pin(21), sda=machine.Pin(20)); "
"print(i.scan())"]
)
addresses = json.loads(result.decode().strip())
assert 0x6E in addresses

6. Deployment USB

#!/bin/bash
set -e

PORT=/dev/ttyACM0

echo "🔥 Syncing files to device..."
mpremote connect $PORT fs cp src/main.py :main.py
mpremote connect $PORT fs cp -r src/drivers :drivers

echo "🔄 Reset"
mpremote connect $PORT reset

7. Deployment OTA

7.1 Minimalny klient OTA

import urequests, machine, os

UPDATE_URL = "http://192.168.1.2/firmware/main.py"

def download_and_update():
response = urequests.get(UPDATE_URL)
with open("main_new.py", "w") as f:
f.write(response.text)
os.rename("main_new.py", "main.py")
machine.reset()

7.2 Skrypt OTA (Jenkins → NAS)

import requests

url = "http://nas.local/micropython/build/main.py"
r = requests.put(url, data=open("src/main.py", "rb"))
print("Uploaded:", r.status_code)

8. Jenkinsfile – pełny pipeline

pipeline {
agent any

stages {

stage('Checkout') {
steps { checkout scm }
}

stage('Install') {
steps {
sh \"\"\"\n pip install -r requirements.txt\n \"\"\"\n
}
}

stage('Lint') {
steps {
sh "ruff check src"
}
}

stage('Unit tests') {
steps {
sh "pytest -q --disable-warnings --maxfail=1"
}
}

stage('Package') {
steps {
sh "mkdir -p build && cp -r src build/"
}
}

stage('Deploy USB') {
when { branch 'main' }
steps {
sh "bash scripts/deploy_usb.sh"
}
}

stage('Deploy OTA') {
when { branch 'release' }
steps {
sh "python3 scripts/deploy_ota.py"
}
}
}

post {
always { archiveArtifacts artifacts: 'build/**' }
}
}