2026年5月4日 星期一

沒事把node.js簽發的jwt拿來呼叫.net core web API時踩了一堆坑的故事

 照理說dot net 的 web api與nextjs整合應該是前端用Browser -->next.js --> .net web api (讓dot net的microservice放在另一個網段)
不過如果直接讓node.js簽發給browser client的jwt拿來call .net web api會發生什麼事 (為什麼要這樣做?不為什麼,就只是想知道罷了)

這一路走來我遇到了狀況,因為很有趣,所以把它做個記錄.

狀況一.401錯誤--起源是錯用了AOT,而不是傳統的Web API專案
原因: VS 2026 建專案時選到了 "Web API (Native AOT)" 模板,使用 CreateSlimBuilder + PublishAot。AOT 的 trimmer 會把 JWT 需要的 crypto provider 砍掉。



所以最初在設計Web API時,一定要選對專案型態 ,不是AOT,而是最上面那種傳統Web API型態的專案
AOT與一般web API專案的差別:

                    Regular              AOT
─────────────────────────────────────────────────
Builder           CreateBuilder        CreateSlimBuilder
編譯方式           IL → JIT             直接編譯成機器碼
啟動速度           ~500ms+              ~50ms
發布大小           需要 Runtime          單一檔案 (~10MB)
Reflection         ✅ 完整支援           ❌ 受限
JSON 序列化        System.Text.Json      需要 Source Generator
JWT/Crypto         ✅ 全部可用           ⚠️ 可能被 Trim 掉
NuGet 相容性       幾乎全部              部分不支援
適用場景           一般 API、企業應用     微服務、Serverless

-----------------------------------------------------------------------
(小的沒在用AOT,如有先進有補充建議的亦請不吝賜教)

重新增加一個web api project,修改program.cs
-----------------------------

...
...
var
key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"]) );
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = false, ValidateAudience = false, }; });
....
...
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
-----------------------------
Action in Sample controller
-------------------------
[Authorize]
[HttpGet]
public IActionResult Get() {
    var username = User.FindFirst("username")?.Value;
    var role = User.FindFirst("role")?.Value;
    return Ok();
}
----------------------------
之後....
狀況二.錯誤訊息 IDX10517: Signature validation failed. The token's kid is missing.
------------------------------------------
原因: .NET 10 的 Microsoft.IdentityModel 套件收緊了驗證規則,要求 token header 中必須有 kid(Key ID)。而 Node.js 的 jose 預設不產生 kid
解法:
這要在nextjs 簽發JWT及解析端(.net core web API)都要再加一個kid
------JWT.ts-----------
export async function signJWT(user:AuthUser):Promise<string> {
    return new SignJWT(
       {
        id:user.id,
        username:user.username,
        role:user.role
       }
    ).setProtectedHeader({alg:'HS256') <--Before
    ).setProtectedHeader({alg:'HS256', kid: 'MyKidKey'}) <--after.
    .setIssuedAt()
    .setExpirationTime('8h')
    .sign(SECRET); <---SECRET是從.node 設定檔來的,跟program.cs的 Configuration["JWT_SECRET"]相呼應
}
------Program.cs------
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"])
);
key.KeyId = "MyKidKey";  <----KID set here.
--------------------------
這下子,總該可以了吧?
再次用postman設定好bearer token後,試著request controller 時
出現新的錯誤錯誤--
狀況三:「The signature is invalid」錯誤
這東西真的是超難解的,最後是把兩邊的code都送給AI去看的結果才知是「長度」問題:

原因: HS256 要求 key 至少 256 bits(32 bytes)。原本的 secret 只有 27 字元,jose 和 .NET 對不足長度的 key 可能有不同的 padding 行為。

解法: Secret 改成 32 字元以上,兩邊同步更新。

最後,終於在controller的GET action中,可以讀取到User資訊了
但是「User.FindFirst("role")?.Value」回傳null,這樣還是不完成,
改成「User.FindFirst(ClaimTypes.Role)?.Value」,就會得到role值了

別忘了,在[Authorize...]指定需要的角色權限.ex  [Authorize(Roles = "manager,admin")]

有些知識真的是靠踩坑得來的,不是AI直接給你就會了解的.





2025年1月16日 星期四

天啊,Serilog只輸出到console( for .net 6),卻沒存到file ?如何是好?

 不要在appsettings.json的Serilog區段改path了,那個沒用,
直接在Program.cs裡改才有用啦

===============
       string exePath = AppDomain.CurrentDomain.BaseDirectory;
        string logFolderPath = Path.Combine(exePath, "logs");
        string logFilePath = Path.Combine(logFolderPath, "log-.txt");
        // Ensure the logs folder exists
        if (!Directory.Exists(logFolderPath))
        {
            Directory.CreateDirectory(logFolderPath);
        }
        // Configure Serilog
        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .WriteTo.File(
                path: logFilePath,
                rollingInterval: RollingInterval.Day,
                outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
            )
            .CreateLogger();
================

試了很久才試出來.....不知道之後的版本有沒有改掉這個bug

2024年12月13日 星期五

for infomix,string to date,and get diff days count

 假設create_date是以yyyyMMddHHmmss的方式存放...  
呃...這樣說不好懂,假設是20241113134118 這樣的方式存放的文字
如何查出與系統目前時間差多少天?
 「我知道,問chatGPT....」
最好是問了會有用,就是因為沒用我才在這做筆記
首先,你要先把日期字串轉成日期物件
這是就要用to_date函數:
注意格式字串
 to_ Date(hn_create_date, "%Y%m%d%H%M%S")
很巧,只c#剛好相反,別想錯了

「那"差幾天"要怎麼取值?」
「人客你住巷仔內的--內行哦,這就是重點了」

如果直接用current-to_date(....)
你會得到一個「12 11:22:33.000」這樣的東西
是的就是幾天 然後幾旳幾分幾秒的東西,那東西叫interval 
偏偏這東西就是麻煩,要先轉成字串再轉其他方式使用
直接給你答案了

cast(
trim(
    (
     current-
     to_ Date(create_date, "%Y%m%d%H%M%S")
    ):: INTERVAL day (9) to days varchar (10)
   )
) as integer  .... 

(然後看你要加上什麼天數限制條件了)

參考
to_date
還有這個
interval to char

OS:不知不覺從砍人式系統來到金融圈已經一年半了.應該是回不去了


2024年10月3日 星期四

pi zero2w to setup QT5.14

ref:
https://www.waveshare.net/study/article-1042-1.html

2024年我的pi zero 2W裝的是bulleye,但QT官方說QT4只有buster,沒有bulleye

問題,與排除:

1「sudo apt-get build-dep libqt5gui5」會遇到「you need to put some deb-src URIs in your sources.list」錯誤,要你加入apt source

此時,請到「/etc/apt/sources.list」把
「#deb-src http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi」
的「#」拿掉,再用apt-update,才會找到套件

之後,請務必照以下的步驟找到/置放設定檔
-------------------------

下载树莓派的编译配置文件

~ $ git clone https://github.com/oniongarlic/qt-raspberrypi-configuration.git

放到Qt的源代码中。具体为:

common/raspberrypi.conf 放到 /qtbase/mkspecs/common/

linux-rpi2-g++ , linux-rpi3-g++ , linux-rpi-g++ , linux-rpi-vc4-g++ , linux-rpi4-v3d-g++ 这4个文件夹放到 /qtbase/mkspecs/中

------------------上述這過程一定要做,別偷懶!!-------

2.configure的指令要改一下,因為不是用pi4,是用pi zero,所以要小改

(其中build $是路徑,不是指令)
----------------------

build $ PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig \

../qt-everywhere-src-5.14.2/configure -platform linux-rpi-g++ \

-D__ARM_ARCH_5TEJ__ \

-v \

-opengl es2 -eglfs \

-no-gtk \

-opensource -confirm-license -release \

-reduce-exports \

-force-pkg-config \

-nomake examples -no-compile-examples \

-skip qtwayland \

-skip qtwebengine \

-no-feature-geoservices_mapboxgl \

-qt-pcre \

-no-pch \

-ssl \

-evdev \

-system-freetype \

-fontconfig \

-glib \

-prefix /opt/Qt5.14 \

-qpa eglfs \

-qt-xcb

----------------------- 

3.make中,遇到錯誤
  cannot find -lGLESv2 
   cannot find -lbrcmGLESv2
解法:請到/home/john/qt-everywhere-src-5.14.2/qtbase/mkspecs/commonqt-everywhere-src-5.14.2/qtbase/mkspecs/common修改「raspberrypi.conf」
-----

QMAKE_LIBS_EGL          = -lbrcmEGL -lbrcmGLESv2

QMAKE_LIBS_OPENVG       = -lbrcmEGL -lbrcmOpenVG -lbrcmGLESv2

QMAKE_LIBS_OPENGL_ES2   = -lbrcmGLESv2 -lbrcmEGL

QMAKE_LIBS_BCM_HOST     = -lbcm_host


EGLFS_DEVICE_INTEGRATION = eglfs_brcm

-----
save後,再去 make

4.再make,再遇到錯誤
----------------------------

qeglfsbrcmintegration.cpp:74:5: error: ‘EGL_DISPMANX_WINDOW_T’ was not declared in this scope

   74 |     EGL_DISPMANX_WINDOW_T *eglWindow = new EGL_DISPMANX_WINDOW_T;

----------------------------
這要改一點地方,先用bulleye xwin之下的檔案總管找「eglplatform.h」
每一個eglplatform.h都加上這個
-----

typedef uint32_t DISPMANX_ELEMENT_HANDLE_T;
typedef struct {
  DISPMANX_ELEMENT_HANDLE_T element;
  int width;   /* This is necessary because dispmanx elements are not queriable. */
  int height;
} EGL_DISPMANX_WINDOW_T;

-----
然後,把這個檔案qeglfsbrcmintegration.cpp  蓋到 /home/john/qt-everywhere-src-5.14.2/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_brcm 裡去
(當然,這還是會有error,所以...也要把「qeglfsbrcmintegration.h」加上上面那些typedef...宣告,才能繼續給他make下去)

------------

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qeglfsbrcmintegration.h"
#include <bcm_host.h>


QT_BEGIN_NAMESPACE

static DISPMANX_DISPLAY_HANDLE_T dispman_display = 0;

static EGLNativeWindowType createDispmanxLayer(const QPoint &pos, const QSize &size, int z, DISPMANX_FLAGS_ALPHA_T flags)
{
    VC_RECT_T dst_rect;
    dst_rect.x = pos.x();
    dst_rect.y = pos.y();
    dst_rect.width = size.width();
    dst_rect.height = size.height();

    VC_RECT_T src_rect;
    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = size.width() << 16;
    src_rect.height = size.height() << 16;

    DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0);

    VC_DISPMANX_ALPHA_T alpha;
    alpha.flags = flags;
    alpha.opacity = 0xFF;
    alpha.mask = 0;

    DISPMANX_ELEMENT_HANDLE_T dispman_element = vc_dispmanx_element_add(
            dispman_update, dispman_display, z, &dst_rect, 0, &src_rect,
            DISPMANX_PROTECTION_NONE, &alpha, (DISPMANX_CLAMP_T *)NULL, (DISPMANX_TRANSFORM_T)0);

    vc_dispmanx_update_submit_sync(dispman_update);

    EGL_DISPMANX_WINDOW_T *eglWindow = new EGL_DISPMANX_WINDOW_T;
    eglWindow->element = dispman_element;
    eglWindow->width = size.width();
    eglWindow->height = size.height();

    return (EGLNativeWindowType)eglWindow;
}

static void destroyDispmanxLayer(EGLNativeWindowType window)
{
    EGL_DISPMANX_WINDOW_T *eglWindow = (EGL_DISPMANX_WINDOW_T*)(window);
    DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0);
    vc_dispmanx_element_remove(dispman_update, eglWindow->element);
    vc_dispmanx_update_submit_sync(dispman_update);
    delete eglWindow;
}

void QEglFSBrcmIntegration::platformInit()
{
    bcm_host_init();
}

static int getDisplayId()
{
    // As defined in vc_dispmanx_types.h
    // DISPMANX_ID_MAIN_LCD     0
    // DISPMANX_ID_AUX_LCD      1
    // DISPMANX_ID_HDMI         2
    // DISPMANX_ID_SDTV         3
    // DISPMANX_ID_FORCE_LCD    4
    // DISPMANX_ID_FORCE_TV     5
    // DISPMANX_ID_FORCE_OTHER  6 /* non-default display */
    static const int dispmanxId = qEnvironmentVariableIntValue("QT_QPA_EGLFS_DISPMANX_ID");
    return (dispmanxId >= 0 && dispmanxId <= 6) ? dispmanxId : 0;
}

EGLNativeDisplayType QEglFSBrcmIntegration::platformDisplay() const
{
    dispman_display = vc_dispmanx_display_open(getDisplayId());
    return EGL_DEFAULT_DISPLAY;
}

void QEglFSBrcmIntegration::platformDestroy()
{
    vc_dispmanx_display_close(dispman_display);
}

QSize QEglFSBrcmIntegration::screenSize() const
{
    uint32_t width, height;
    graphics_get_display_size(getDisplayId(), &width, &height);
    return QSize(width, height);
}

EGLNativeWindowType QEglFSBrcmIntegration::createNativeWindow(QPlatformWindow *window, const QSize &size, const QSurfaceFormat &format)
{
    Q_UNUSED(window)
    return createDispmanxLayer(QPoint(0, 0), size, 1, format.hasAlpha() ? DISPMANX_FLAGS_ALPHA_FROM_SOURCE : DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS);
}

void QEglFSBrcmIntegration::destroyNativeWindow(EGLNativeWindowType window)
{
    destroyDispmanxLayer(window);
}

bool QEglFSBrcmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
    switch (cap) {
        case QPlatformIntegration::ThreadedPixmaps:
        case QPlatformIntegration::OpenGL:
        case QPlatformIntegration::ThreadedOpenGL:
        case QPlatformIntegration::BufferQueueingOpenGL:
            return true;
        default:
            return false;
    }
}

QT_END_NAMESPACE
-------------------------------------
之後就沒遇到什麼錯誤(然後花了三天才make 完)
先裝起來,日後試出在他台ubuntu做cross link & 在EGFL模式下跑都正常的話,再來向大家報告

------2024/10/31:颱風天,讀書天--繼續玩我們的QT大法-------
在ubuntu 22.04 做Cross Link的設定,可參考這篇,我測過可用
https://blog.csdn.net/weixin_44658484/article/details/118655494

(在這次的建構中,我是把zero 2W設定為跟pi3一樣等級的板子,
所以configure時, -device是以「-device linux-rasp-pi3-g++」設定的)
惟一要注意的是在configure階段時會遇到類似以下的訊息:
/qfloat16.h:300:7: error: 'numeric_limits' is not a class template
300 | class numeric limits<QT PREPEND_NAMESPACE(qfloat16)> : public numeric limits<float>
這時要在qglobal.h加入<limits>,不是隨便加,要看#if條件加的
----------------
#ifdef __cplusplus
#  include <type_traits>
#  include <cstddef>
#  include <utility>
#  include <limits>
#endif
----------------
,save後,再去configure,就正確了
之後,你會在make階段會遇到「 : error: ‘EGL_DISPMANX_WINDOW_T’ was not declared in this scope 」錯誤
沒錯,就是照之前寫的那樣去改.h & .c 檔,就可以完成make & make install了

(颱風後要去修屋簷了...)



2024年6月25日 星期二

在BBB中安裝TOTOLINK A650UA驅動程式

sudo apt-get install dkms

sudo apt install -y linux-headers-$(uname -r) build-essential bc dkms git libelf-dev rfkill iwsudo apt install -y linux-headers-$(uname -r) build-essential bc dkms git libelf-dev rfkill iw


git clone https://github.com/brektrou/rtl8821CU
cd rtl8821CU
make 
sudo make install
參考
還有這個
(裝完driver記得重開機才會載入driver,用ifconfig才會看到「wlan0」)

2024年4月24日 星期三

在ubuntu啟動.net core webapp https時,遇到「No server certificate was specified, and the default developer certificate could not be found or is out of date.」如何解決

 這個錯誤的全文是這樣的:

-----------------

sudo dotnet 你的DotNetMvcApp.dll
crit: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to start Kestrel.
System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at 你的DotNetMvcApp.Program.Main(String[] args) in P:\你的DotNetMvcApp\你的DotNetMvcApp\Program.cs:line 49
Aborted
-----------------
這時,你可以用這些指令去重新產生並查詢憑證
----
dotnet dev-certs https --clean
dotnet dev-certs https
sudo dotnet dev-certs https --check --verbose
---
參考這個連結
大家一起加油吧

2023年10月28日 星期六

informix之.net core EF 套用.

 直接參考這文章做就差不多了
很多人會卡在Scaffold-DbContext這一關
重點是要注意一下PORT ,因為informix EF Core用的是DRDA的port,所以不是之前我們用odbc的(預設)9088而是9089.
如同上述內文說的:Please note: The IBM Data Server providers only works with the Distributed Relational Database Architecture™ (DRDA) protocols, and you therefore need to ensure that your Informix server is configured accordingly.


(這點你要跟你們家的DBA問一下給不給連,也要問網管開不開port,說真的,我也沒把握一次要他們從DEV、UAT到Production這樣一次開這麼多東西給你,所以本單元真的就是自己練功用的)


上述超連結提到的package有點舊了,我實驗的package有update,也都正常
-----------project target framework是.net 6.0------------------



-----------------------------
這是我localhost的scaffold-Dbcontext script,留做我日後小抄用
------------------------------------------------------------- 
Scaffold-DbContext -Connection "user id=informix;server=localhost:9089;database=db_with_log;Password=xxxOOO" -Provider IBM.EntityFrameworkCore -OutputDir Models  -Force -UseDatabaseNames -Tables test_user,test_data

--------------------------------------------
彩蛋一下:
這份PDF不錯,如果你是.net+informix的重度使用者,可以看一下

OS:明明用到的機會渺茫.我還是堅持到底地把它試出來玩了幾番,這也是種職業病吧





2023年10月20日 星期五

拜見老前輩--informix使用記錄(.net 4.5用)

最近因為案子需要,所以接觸到informix

為此在本機(win11)安裝了informix develper version
照理說應該不需要管到informix server的管理,這應該是DBA的工作
不過如果是本機開發測試需要,真的都要自己來

註:以下是為ODBC+.net4.5準備的操作,如果是可以用 .net core開發informix應用的話,請用entity framework core即可,IBM有為.net core發行了相關的EFcore套件.比較不必費心在transaction控管上面.既然上面要求要用.net4.x就老老實實配合就是了

----言歸正傳-----
安裝informix develper server不是問題,我們是用Odbc連線做之後的增刪修查
(因為這樣日後移機會比較好改比較簡單,只要做odbc 設定就好)
雖說「比較簡單」,一點也不簡單
首先來說,你要怎麼測能不能連,當然是用他們自家的工具--ConnectTesst Demo 測比較快

(以上的database下拉可以選才是真的連起來了,有驚嘆號沒選項就是沒連好,仔細看他的訊息,別使戇力猛試)



如果連不上,或是想把上面的host改成ip的話,請到「C:\Program Files\IBM Informix Software Bundle\etc\sqlhosts.ol_informix1410」去改把host pc name改成 ip

--------------------
ol_informix1410 olsoctcp 127.0.0.1 ol_informix1410 
dr_informix1410 drsoctcp 127.0.0.1 dr_informix1410 
lo_informix1410 olsoctcp 127.0.0.1 lo_informix1410 
--------------------
必要時,連「C:\Program Files\IBM Informix Software Bundle\ol_informix1410.cmd」的內容也要改
(連在C:\Windows\System32\drivers\etc\裡的「hosts」也加上本機名稱跟ip對應也再所不惜地改吧)
--------------------

set INFORMIXDIR=C:\PROGRA~1\IBMINF~1
rem set REGMACHINE=\\你的主機名稱    <--先remark起來,以下用ip替代
set REGMACHINE=127.0.0.1
set INFORMIXSERVER=ol_informix1410
set ONCONFIG=onconfig.ol_informix1410
set INFORMIXSQLHOSTS=C:\Program Files\IBM Informix Software Bundle\etc\sqlhosts.ol_informix1410
set GL_USEGLU=1
set PATH=%INFORMIXDIR%\bin;%PATH%
--------------------
這關過了,之後就是ODBC設定了(基本上,「ConnectTest Demo」 能通的話,這也是可以通了)
「可是只能用英文,我要能支援中文的話,可以嗎」

當然可以,而且你也一定不會在內建的「sysxxx」db裡操作(DBA也會把你趕出去的),我們要用別的工具來create db.免驚啦,免錢的哦,他就是鼎鼎大名的DBVisualizer 而且重點不是只有在creeate db而已,是在其連線過程.
一般只會講到這一頁


但是,為了要設定之後的locale,就要用到這一頁了
(注意上面有三個user setting:DB_LOCALE,CLIENT_LOCALE & charSet,一般是沒這三個設定)

這樣後,用DBvisualizer連入informix後,就可以用它來create db了



然後,再回到odbc設定語系,才不會因為語系不同而無法連入
(照理dba會幫你做好db的建置的,但別忘了註明我要中文在地化支援)
看一下中文語系是不是DB的預設語系


這樣你就可以好好享用ODBC + .net操作了
「可是....我怎麼試,transaction總是auto commit,到底要怎樣才能支援transaction?」
「很好,這表示你真的有在玩下去,你可以出山下山了」
想要用.net的OdbcTransaction ,你不只要在odbc那裡把「Auto Commit Optimization」不打勾之外


(以上這點有沒有 check都沒差)
更重要的是.....
在create db時,log mode一定不能選「none」,好歹有個log模式就是了(不知道就用「with log」)


這樣,才會支援交易機制
上述的設定,請參考以下討論
大家加油吧
(忘了跟這次的要角--IBM informix ODBC Driver Setup合影一下,來,笑一個吧)





2023年7月5日 星期三

小試一下,FSMC之於LCD(NT35510),在F103的設定(by Stm32CubeMx)

  雖然用GPIO會比較快,也可以省卻「如果你的chip不支援FSMC...」的問題

不過多學會點會比較好些,也許哪天會用到也不一定,
本篇參考以下兩個URL:這個 & 這個
NT35510的規格書在此

先上圖吧


(別忘了Back Light要On起來,沒有back light,沒有顯示)
先用read ID & color fomat測看看,如果讀出是亂碼,請到上圖的NOR/PSRAME timing for wirte access那裡改一下,有時要多點clock才會正確 (參考上面兩個URL的內文介紹如個推算,因為我是用NT35510的,所以會以第二個link為主)

程式碼如下
---------------------------------------------------
//因為是用base1 第 4區,所以base address是0x6C000000
 (即0x60000000~0x6FFFFFFF分成區,第四區起始位址即為0x6C000000,參考「STM32F103 战舰开发指南V1.1.pdf」P365)
#define FMC_ADDR_CMD  ((uint32_t) 0x6C000000)
//而A10做為C/D(或曰register select),又要往左shfit一位,故為00800
#define FMC_ADDR_DATA  ((uint32_t) 0x6C000800)
//在 MX_FSMC_Init();後,這些對應建立起來後,就可以直接用位址的方式讀寫,不必再用8080介面操作了...
void lcd_wr_cmd(volatile uint16_t cmd)
{
    cmd = cmd;
    *(uint16_t *)(FMC_ADDR_CMD) = cmd;
}
void lcd_wr_data(volatile uint16_t data)
{
     data = data;
*(uint16_t *)(FMC_ADDR_DATA) = data;
}
uint16_t lcd_rd_data(void)
{
volatile uint16_t ram; 
ram = *(uint16_t *)(FMC_ADDR_DATA);
return ram;
}
...
...
main的內容
    HAL_Init();
   MX_FSMC_Init();
   MX_USART1_UART_Init();
 //神說,要有光,於是就有了光
 HAL_GPIO_WritePin(LCD_BL_GPIO_Port,LCD_BL_Pin,GPIO_PIN_SET);
  HAL_Delay(10);
  lcd_wr_cmd(0x1100);    //Sleep out & booster on 
  HAL_Delay(10);
  lcd_wr_cmd(0x2900);   //display memory  to frame.
  HAL_Delay(10);
  //第一步,先試著讀id,如果傳回值不是80h的話,就是錯了
  lcd_wr_cmd(0x0401);
  HAL_Delay(100);
  uint16_t chip_id=0xabcd;
  chip_id=lcd_rd_data();
  
  //試著讀寫 color format,原先是8x8x8 RBG格式,如今我們為了測試,用565格式
  //0x0C00是read color mode,而0x3A00是write color mode.
  uint16_t pix_fomrat_org=0;
  lcd_wr_cmd(0x0c00);pix_fomrat_org=lcd_rd_data();
  lcd_wr_cmd(0x3A00); lcd_wr_data(0x55);
  lcd_wr_cmd(0x0c00);
  uint16_t pix_fomrat=lcd_rd_data();
   //清除資料一下 (預設尺寸是480*800)
  lcd_wr_cmd(0x2c00);; //demo how to do "clear screen"
  for(uint32_t i=0;i<(0x01df-1)*(0x03ef-1);i++){
      lcd_wr_data(0x07E0);
  }
  
  //最後,把大頭照放上去 x,y : 200,200, width& height:140x140
  lcd_wr_cmd(0x2A00);lcd_wr_data(0);;
  lcd_wr_cmd(0x2A01);lcd_wr_data(200);
  lcd_wr_cmd(0x2A02);lcd_wr_data(339>>8);
  lcd_wr_cmd(0x2A03);lcd_wr_data(339&0xff);
  
  lcd_wr_cmd(0x2B00);lcd_wr_data(0);
  lcd_wr_cmd(0x2B01);lcd_wr_data(200);
  lcd_wr_cmd(0x2B02);lcd_wr_data(339>>8);
  lcd_wr_cmd(0x2B03);lcd_wr_data(339&0xff);  
  lcd_wr_cmd(0x2c00); //demo how to do "clear screen"
  for(uint32_t i=0;i<(140*140);i++){
      lcd_wr_data(g_pexl[i]);
  }
  
 //然後就是while(1)...

---------------------------------------------------
 耶~ 有看到圖片了(會不會太自戀了?)
------




------
這次的實作基本上只是個小插曲,還是建議用GPIO直接去操作,以清除螢幕這個函數來說好了
-----------
void fill_color(uint16_t color, uint32_t count)

   //當然以下的HAL_GPIO_WritePin都可以再直接操作BSRR暫存器來最佳化之,
  //只是為了實作驗證是否了解正確,直接用HAL程式庫了
    set_d0tod7_output();
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_RESET);//RS:0
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
    HAL_GPIO_WritePin(LCD_READ_GPIO_Port,LCD_READ_Pin,GPIO_PIN_SET);//RD 1
    HAL_GPIO_WritePin(LCD_CS_GPIO_Port,LCD_CS_Pin,GPIO_PIN_RESET);//CS 0
    
    write_byte_to_gpio(0x2c00);
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_RESET);//write 0
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
    
    //set to data mode
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_SET);//RS:1
    write_byte_to_gpio(color);
    uint32_t pin16u=(uint32_t)LCD_WRITE_Pin<< 16u;
    for(uint32_t i=0;i<count;i++)
    {
         LCD_WRITE_GPIO_Port->BSRR = pin16u; //RESET
         LCD_WRITE_GPIO_Port->BSRR = LCD_WRITE_Pin;//SET
     }
    //all done
    HAL_GPIO_WritePin(LCD_CS_GPIO_Port,LCD_CS_Pin,GPIO_PIN_SET);//CS 1
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_SET);//RS:1
    HAL_GPIO_WritePin(LCD_READ_GPIO_Port,LCD_READ_Pin,GPIO_PIN_SET);//read 1
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
}
-----------
這比填入FSMC再填入LCD快了點(直接高低電位切換,當然快啊,這樣比很不講武德捏)
而且會看技術規格文件,是這行必備的生存技能
當然FSMC會了也是很好的一件事,畢竟很多時候可以對應到別的設備的,大大節省開發難度
要學的東西真的太多了,一起加油吧.

2023年6月18日 星期日

For Xamarin.android 如何用Android.Hardware.usbconnect來做HID 的write report類似的動作

 最近拿到一個設備做整合的案子,是USB-HID介面
跟廠商要到的sample code是用HidSharp的寫.
-------------------------------
                    var device = HidDevices.Enumerate(vendorId, productId).First();
                    dev.OpenDevice();
                    const UInt32 ZEROS=0x00;
                    const UInt32  Heading04=.....;
                    var bytes=new List<Byte>();
                    bytes.AddRange(BitConverter.GetBytes(Heading04).Reverse().ToArray());
                    bytes.AddRange(BitConverter.GetBytes(ZEROS).Reverse().ToArray());
                    //to write data to HID,use "report" as pocket
                    var outputReport = dev.CreateReport();
                    outputReport.ReportId = 0;
                    outputReport.Data=bytes.ToArray();
                    dev.WriteReport(outputReport);
-------------------------------
今欲轉移到安卓設備上,一直找不到HID 類似的實作.
「既然沒有,就自己包了」,不過HID真的是很不單純的規格(光是USB就有一堆書跟規格要看了),談個容易
「不然就觀察看看上述的code,在USB介面是如何動作的?」
 這時就要借助好用的工具「DeviceMonitor Studio」(試用期14天,動作快!!)
因為我們知道是用USB的HID規格,所以監視方式請用HID等選項


不過,真正比較有用的是在packet view的狀態.....

嗯,看那裡面的參數Request、Value、index、data.... ,真的是「火燒豬頭-- 面熟面熟咧」 
「阿這不就是UsbConnection的ControlTransfer嘛」
「好啊,那你試看看用controlTransfer會不會傳回-1?如果不是-1表示成功了」
先讀看看Descrption看看,如果可能,就可以把其他動作給它(controlTransfer)來做了
xamarin.android ControlTransfer的API在此
「我試了UsbAddressing.In的方式一直讀取不了資料,心很累....」
別一開始就埋頭苦幹用戇力(gōng-lak)--先找成功案例的code來改來測才好比對驗證
先以這篇討論的例子來改
---
UsbDeviceConnection.controlTranfer(0x81, 0x06, 0x2200, 0x00, byte[] buffer, 104, 2000)
----
「第一個參數不是UsbAddressing型態嗎?怎麼會是一個叫0x81的整數?」
「儘信API不如無API,更何況那是從java包裝來的,直接把0x81 cast成UsbAddress就是了」
所以用以下的code來測,結果傳回值不再是-1,而是37(是的buffer 裡也有值了,我出運啊,我出運啊啦...)
-------------------------
var returvalue = conn.ControlTransfer((UsbAddressing)0x81, 0x06, 0x2200, 0,buffer,200,int.MaxValue);
//returnvalue為37
--------------------------
「嗯....又是另一個問題了,,為什麼是0x81,而不是UsbAddress.In? 為什麼用UsbAddress.In & UsbAddress.Out傳回都是-1 ...為什麼」
「請把剛才那篇討論文看完,答案就在大師回覆的連結裡」
參考這段文章吧,為什麼address選0x81? 因為bit7 & bit0是1,表示為Device to host(Interface)
這個bmRequestType表很重要,他是問題核心.


為什麼request用0x06? 因為要讀Description


綜上所知,如果要把我們在DeviceMonitor studio的packet所看到的動作,用controlTransfer來包裝的話
那address要選什麼?.....答案就在畫面裡....


試試看把Request & Value都改成我們要傳送的資料,而UsbAddress改成0b00100001看看
(即表"bmRequestType"裡的"interface" & "class"兩個bit都要on起來)
--------------------------
List<Byte> bytes = new List<Byte>(0);
              ......
                bytes.AddRange(BitConverter.GetBytes(Heading04).Reverse().ToArray());
                bytes.AddRange(BitConverter.GetBytes(ZEROS).ToArray());
                returvalue = await conn.ControlTransferAsync((UsbAddressing)0b00100001, 0x09, 0x0200, 0, bytes.ToArray(), bytes.Count, int.MaxValue);
                System.Console.WriteLine($" return: {returvalue}");
                await Task.Delay(10);
---------------------------
returnvalue不再是-1,也很正確地輸出訊號到HID上了
這次真的很難得的經驗啊
一起加油吧