ceezblog.com

I like cat, isn't it obvious?

Author: Simon

  • 13 reasons why I hate Windows11

    13 reasons why I hate Windows11

    Here are 13 reasons I hate about windows 11

    1. If not for the only frigging app I need for work, FUsion, that stops support for windows 10. No update for a cloud based app means no more access to cloud data. Yes, FUsion means Fk-You-sion. I start the fusion by hitting [win] + [F] + [U] + [enter]. That is how much I hate FUsion and Windows11. It was fusion 360 and I used to hit [win] + [3] + [6] + [0] + [enter] using the numpad. I wish I could change the CAD software, but an old dog like me can no longer learn new trick.
    2. Windows 11 is much sluggish compare to windows 10. That was my first impression on my old PC: Ryzen5 5600G + 16GB RAM + 1660 super. That was a decent computer not as low budget as it seem.
    3. I got bitten by bitlocker, twice, until I realized bitlocker was enabled by default without telling that during fresh installing windows 11. Yes, lost data and banging my head thinking it was because of hard drive failure. Bitlocker won’t be enabled if you are upgrading from Windows 10, that with bitlocker disabled of course. My bitter experience tells me, it is just a frigging ticking time bomb. Imagine that you store 1 bitcoin in the secured usb stick and somehow you lose the key to unlock the usb stick, yeah that was my feeling, except I am not that rich.
    4. I understand that TPM is essential for bitlocker, but I don’t want bitlocker in the first place so why do you make it compulsory to install windows 11? Is that the guaranteed way to f*ck up my data when the motherboard fails? Then people will laugh on you because you didn’t backup the keys inside TPM for that worst case scenario.
    5. Why there is a tickbox on the icon of a file? It just confuses everyone, both new windows users or veterans.
    6. Why would I need a microsoft account in order to login my PC. Why would I have to jump through hoops and loops in order to login windows the first time without MS account. Are you that data hungry that you want to harvest data from every single windows user? Why would you keep telling me to log in with ms account?
    7. Why would you pop the backup reminder screen after every single windows update to sell 365 subscription package? Are you that saleperson who’s so clingy to a customer desparately? Why would I want one drive since I have my own server and backup server?
    8. Why you keep deleting my administrative share after every single windows update, which happens only after a few days. This was so annoying to the point I gave up administrative share and built a dedicated NAS to share file around my homelab instead. Are you deeming administrative share unsecure and force the user doing your way, just like how Bambu “BS” controversy about bambu connect. FIY, “BS” is both Bambu Studio and bullsh*t at the same time in my vocabulary, or should I say “BS BS” instead.
    9. Why would I have to click on show more options on the popup menu which supposes to be more convenience? Do you understand the copy, cut, paste icons are a lot harder to spot and click with 2K monitor even though the monitor is 29″, not to mention portable 11″ monitor with the same 2k resolution.
    10. Why would I have to jump through hoops and loops just to disable a NIC or change DNS settings? What’s wrong with the old school control panel so that you are phasing it out?
    11. We use mspaint because it is simple, it is pixel-based editor. Why would you keep “upgrading” it the worst way possible? “Upgrade” here is in air-quote! Why would you assign a moron programmer to “upgrade” mspaint like that? If I want AI stuffs, I would go with photoshop or go cheap with MS photos app. Isn’t photos app is not enough so that you have to bring AI stuffs into mspaint? When I think of free solution to do with photo editing for website or uploading 3D designs to printables or makerworld for example, such task as adding text, adding arrows, descriptions… to the photo, my first thought is Photoscape v3.7 although it doesn’t have AI sh*t like background removal. You want the photo as real as possible because people hate AI generated photo.
    12. Why would you remove 3D builder. It was very useful app. It did help me a lot during my first days with 3D printing stuffs. It seems like you are trying to get rid of traces of windows 10 out of windows 11.
    13. I’m still thinking about #13, might edit the post later. For now the obvious reason is to make the title sound like “13 reasons why” show series or the song “10 things I hate about you”. So, that’s that.

  • Building desktop PC with mobile chip 7945HX

    Building desktop PC with mobile chip 7945HX

    Why I want a new PC with mobile chip

    As recently, my 5 years old PC’s showing age: bsod and crash during web browsing. It was R5 5600G pairing with 1660 super, a mid-range budget build at the time. I don’t know what causes the crash, but I am not really interested in finding out. It’s served me well during the years. So yeah, let put it to rest and build a new PC.

    Choosing self-built PC instead of pre-built PC

    I’ve been contemplating for almost a month what CPU for my next PC. Thinking Strix Halo would be the best as the iGPU (8060s) is quite on-par with mid-tier discrete GPU, the like of RTX 4060. But the price is quite steep, such as Framework desktop Max+ 395 – 64GB for ~AU$2900 minimum. Let round it up to AU$3000 as you have to buy accessories and stuffs as well.

    I don’t care much about 10-20% CPU processing power difference, it still is huge improvement over my old PC. My work is about 2D and 3D cad (kicad + fusion) mostly. I also do some light gaming for entertainment.

    As for the budget AU$3000, I could build a better PC compare to Strix Halo. Just some rough estimate for AM5 R7 9700X system:

    • 600 for GPU
    • 600 for CPU
    • 300 for Motherboard
    • 300 for RAM
    • 300 for case + PSU
    • 300 for SSD

    That was around AU$2400 and I still had budget for better CPU or better GPU. Don’t get me wrong, I am not complaining about Framework desktop being overprice, because it isn’t. Of course, they have to make profit out of low volume product and that price point is quite good actually. Compare to HP Z2 Mini G1a for AU$3400 with lower specs, it is actually plenty cheap

    I could go with BD790i X3D with Zen 4 7945HX-3D (about 5-10% processing power lower from Zen 5 395+ AI Max), and still have ITX form factor (same as framework motherboard, but without the grunt of iGPU).

    The size is 170x170mm, very compact and small. But I have to pair it with a discrete GPU as well as SFF/SFX PSU which is not that small to fit into tiny case. I did have a thought about some fancy cases like Thermaltake tower 250, though it is huge considering support only ITX motherboard

    But then stepped down a notch, choosing mobile chip 7945HX coming with BD795M. I was lucky to get the low price before it was jacked up by tariff or whatever. And 3 weeks later, this is the new price almost as twice as much.

    US$360 = AU$546 and US$624 = AU$946 (exchange rate at the time of this post)

    Compare to R7 9700X, R9 7945HX is about 15% less to 9700X in single core task but overpower by 40% in multi-core task. This is quite easy to understand as it is 16 core Zen4 vs 8 core Zen5. This actually perfect for me. I don’t play game much so that single core task is less important for me, but I’d like to open multiple chrome tabs, like 20-30 tabs at a time.

    I saved about AU$350 out of AU$900 for similar performance.

    Yeah, this is mATX board and it takes SODIMM instead of DIMM. It’s common to use SODIMM in constrain space of small form factor PC, like Dell optiplex micro series, but there are plenty of board space here. It would be better to use DIMM ram instead. There are much more options to buy DDR5 desktop ram, but less options for DDR5 laptop ram.

    I feel funny about this board: AMD CPU but employs Intel 1700 socket heatsink fan. And of course, this spells trouble for me. More to it later.

    So, the TDP of CPU is configurable in BIOS for 75w or 55w, quite power hungry for a mobile CPU (a highend one actually).

    Assembly this PC is very much the same as normal desktop PC

    Except when it isn’t. This is the specs:

    • BD795M motherboard with mobile Ryzen 9 7945HX (soldered)
    • 2×32 DDR5 SODIMM Crucial 5600MT
    • 2TB Kingston NVMe
    • Old wifi module salvaged from broken laptop
    • XFX 9060XT 16GB
    • MSI MAG E240 AIO watercooler
    • 3x Thermalright 120 fan
    • Lian Li A3 mATX case
    • FSP Vita GM 750W (modular)

    The maximum power consumption is roughly 300w (CPU 75w + GPU 160w + FANS + SSD + RAM….). I was aiming for 500w or 550w PSU, say, 40% more for headroom. But for modular PSU, there is nothing below 750w, kinda sad.

    Overall, the process is quite straight forward:

    • sit ram, nvme ssd, wifi card on the board, install CPU bracket / backplate
    • put the board in the case
    • install GPU
    • plug in power cables
    • do cable management a bit
    • install radiator
    • install CPU cooler block/pump
    • install fans
    • boot up and install windows
    • do final cable management

    Nothing worth to note down really

    And here comes the trouble with AIO cooler

    It went smoothly but then I booted into windows the CPU temp was 95°C. I was kinda panic, as leaving CPU burning hot like this is not good. Fortunately, this board already has a beefy heatspreader, to prevent the CPU from release the magic smoke.

    Here is the problem: this AIO bracket/mount thing doesn’t have springs for compression like other waterblock mounting, but a fix-length spacer or standoff.

    The idea is good, as fix-height standoff like that won’t bend the motherboard if you tighten the nut too hard. I am sure it’d work wonderfully for Intel CPU.

    But… this isn’t genuine intel 1700 socket, so the height from the board to the top of the IHS isn’t the same. And that causes thermal throttling as the waterblock never touches the IHS of this CPU. When I removed the waterblock (also pump), the thermal paste, which applied in X pattern, was barely squashed down and still had the same X shape. Bummer! By judging the evidence of thermal paste, there would be about 1-2mm gap. I didn’t take picture of it so you have to take my word for it.

    Futhermore, the standoff doesn’t sit flat on the motherboard. The back plate was rattling around when I was installing the motherboard inside the case. I guess that is why they have 2 strips of double side tape on the backplate to keep it in place not rattling around.

    The solution is easy, 3D print a standoff 18mm height to replace original standoff. That is to reduce about 1.8mm off the height of original standoff of the cooler.

    These 3D printed standoffs combine with M3 screws 40mm long is perfect setup for me. I print this in PC. It has to be impact resistant as well as heat resistance. ABS or ASA may work but not PA (including PA-CF). Because PA does creep too much under compression, that’d potentially undo the nut holding it down.

    Well, if you don’t have a 3D printer or nuts or screws like what I have, then sucks for you. I really can’t imagine how you can deal this situation, probably return the MSI AIO cooler and try something else. I happen to have quite a collection of M3 screws and nylon standoffs for electronics and this designing and solving problem is kind of my work actually.

    You probably get away with 2x 8mm nylon standoff (PA6) + 2x washer + 40mm long M3 screw for each corner. These injection molded nylon part does not creep under compression unlike 3D printed PA6 part. However, it feels like fixing stuff with sticky tape, so that is why the 3D printed standoff instead.

    Yeah, I replace MSI fans to Thermalright TL-S12W since I bought 6 of them for matching style and lighting. Unfortunately, BD795M doesn’t have ARGB controller, which is quite odd. Nowadays all motherboards should have ARGB controller onboard already. I did have a thought to build the controller using arduino plaform since it is just Addressable RGB LED and mostly arduino library would run just fine, but meh! Just buy a off-the-shelf cheap controller and be done with it.

    The system runs very well and cool, and very quiet

    The CPU is cool, as it is watercooled of course. Anyhow, this thanks to great performance of MSI MAG CORELIQUID E240

    TDP of 7945HX is set 75w in bios, which is slightly higher than 65w of most AMD desktop chips but still no problem for MSI MAG E240.

    I really don’t care much about benchmarking number. It is like, your car can do 350 KMH but speed limit here in Australia is 110 KMH for motorways and 50 KMH for local streets. I agree you need some number to tell the performance differences between systems, but I’m just too lazy to install those benchmarking software (haha). A youtuber claims this BD7950M is quite close to Cinebench R23 benchmark of 7950X desktop variant, which is 170w TDP and costs twice as much.

    Compare to my old system, this is much aesthetically pleasing

    Possibly improvement CPU cooler mounting

    For now, the CPU cooler mounting is exactly the same, only the height of standoffs are different. And yes there is a tiny chance of the nut will loosen itself because of thermal cycling. To fully eleminate it, I have a plan to use blinded M3 nuts + springs to have constant pressure, which of course would be better. The blinded nuts still stop it from going down too far. Combine with the same spring at 4 corners, then you’d have same pressure on four corners.

    I saw some dude chipped off a corner of the die of AMD Athlon K7 (Thoroughbred or Barton, I forgot) while installing homemade watercooler, around 2003 or so. Yeah, the good old days.

    And this would complete the build. But for now it is well enough!

    ————————

    If you have the exact same system and want to print the standoff, here is the fusion archive file:

    standoff_msi_cooler_for_DB795M.f3d

    ————————

    Edit 30-09-2025:

    After a week of daily use, I found a small problem that I couldn’t solve: The SATA drive pops in the “safe remove menu” of windows and I eject it by mistake. Actually I was quite careful before I click but I wanted to try and see if it is possible to eject the SATA disk like a USB drive, it did actually eject the SATA drive. So, I had to open the side panel of the case and reconnect the sata cable and *voila* the drive enumerates itself back in the system.

    I tried to look for an option to disable hot-swap feature but failed. I didn’t have high hope anyhow as I knew the BIOS is just bare metal before I bought it.

    Support from MINISFORUM told me that this motherboard does not support SATA hot-swapping (as expected) and thus there is no option to disable it in the BIOS.

    I found a registry hack to hide these SATA drives from Eject menu

    TreatAsInternalPort.Reg
    Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\storahci\Parameters\Device]"TreatAsInternalPort"="01"

    That will hide SATA port 0 and SATA port 1 from Eject menu

    You just save as reg file or open regedit and create a string TreatAsInternalPort with content “01”, DWORD 32 or DWORD 64 type also works but the content is different as byte string {‘0’, ‘1’} convert to DWORD32 = {0x30, 00, 00, 00, 0x31, 00, 00, 00}. You know, DWORD32 = 4 bytes for a single character while byte string = 1 byte for each character.

    Just reboot and enjoy!

  • Let’s join the clippy movement!

    Let’s join the clippy movement!

    If you are old like me, like 30-40 years old. You would know the annoying office assistant Clippy coming with Office 2000. Yep, that was 20-24 years ago!

    Most of us would immediately turn off clippy the moment he showed up. Back in the day, Clippy was nosy but he just wanted to help you. He didn’t collect your data secrectly and sell it for profit.

    But now every big corps collect your data and use that to shove very targeted ads on your face. To name a few: FB, Google, Microsoft…

    Yes, those big coprs actually are spying on you. It’s not a conspiracy like government wants to spy on you or control your mind. It’s already happened and you voluntarily give away your data! It’s your frigging phone!

    Have you wondered how FB making profit if they don’t have any product to sell? You are the product. You are farmed by FB, you are watching personal targeted ads on FB. Yes, FB is cashing out with ads views by you!

    Watch this video

    If you don’t like big corps spying on your activities and slap targeted ads on your face, join Clippy movement!

    But we all have to get together. And the first step is making it public that we know what’s going on. That starts with a clippy, on YouTube. If you’re with me, I ask that you change your profile picture to a clippy.

    Said, Louis Rossmann

    So, this is me joining the movement!

    If you want to print a clippy and put him on your desk, you can get it here clippy-movement-clippy-just-wanted-to-help

    You can change the bubble text as you like using this fusion f3d file clippy_speech_bubble.f3d

  • Revisit my homemade T12 soldering station

    Revisit my homemade T12 soldering station

    TLDR; I upgrade my soldering station. Design and print standalone stand for the handle. Include some ranting and story behind it.

    My first homemade soldering station was made 8 years, more or less. It still works just fine. During the years, I’ve made a few more giving to friends. They haven’t complained, yet! (haha).

    I have opensourced my solder station here https://github.com/ceezblog/T12-Solder-station

    My first soldering station was HYLKKO 936, a Yihua 936 clone and chinesium copy of Hakko FX888D. As you might have guessed it, it wasn’t that bad, certainly an upgrade from cheap soldering iron that has a no temperature control circuitry. It took quite sometime for the iron tip (900M series tip) to get up to temperature. There was no sleep function, pretty much the tip stay hot up to target temperature as long as it powered.

    The problem is, I forgot to turn it off a dozen times. Yes, I left the solder iron 300°C hot for half a day until I turned off the light, going to bed. And just at that moment I saw the only frigging LED of the solder station was blinking on for a brief moment… That only red LED turns on when the temp rising up but off when reaching the correct temp. If it is idling at target temperature, that LED only turns for half a second then off for 10-20 seconds. The good news was it didn’t pump power to the iron tip constantly, but only top it up to keep the temp at target. But still, leaving something burning hot unattended was really really bad idea.

    There are two scenarios here:

    • They forgot to put an extra LED or on the face plate to help user like me to turn it off, or just want shave off $0.001 cost of an LED and a resistor.
    • They don’t care about user experience, the iron does get hot and that is enough, wrap it and sell it

    I tend to think the latter is the case here. This is what China is well-known of, the Chabuduo mindset, google it.

    At some point, I had to make a simple buzzer that beeps every 5 minutes to remind me to turn it off. Now it is permanently a heat set press jig for installing threaded brass insert. It did serve its job to help me build my own soldering station though.

    Recently, I moved house, set up new home-lab and I pretty much rearranged everything. I have 2 active soldering stations + hot air station now, exclude that weird heat set press jig.

    Before that, the soldering iron stand was bolted to the side of the case as one piece, very convenience, having them easy to pack up to go. The case was ABS print, still strong after years of service.

    Imagine when I stack 2 of them on top of each other. Yeah, kind of awkward with the iron stand on the side. Thus, I need a standalone stand for the handle.

    What a big deal? just go buy a stand from aliexpress or go search Makerworld, printable and print one? Yeah, nah.

    I have a custom optical sensor (photointerrupter) to detect if the handle is docking, so it will trigger sleep function of the station. The sleep function will keep the tip around 200°C for 5 minutes then turn off the heater completely afterward. This is just for safegarding for user who forgets to turn off their stuffs like me! So, even the case I forgot to turn soldering station off for days, it’d still be safe, the iron would be room temperature cold.

    When the handle is put in the stand, the black lever is pushed down and blocks the IR beam –> hence it called photointerrupter. Pretty much the same as off-the-shelf part, but I don’t have to make a small PCB to hold it. Making PCB and design a way to mount that photointerrupter was kind too much to do. So I rolled with my custom design, it was easier, I don’t have to go back and forth between fusion and kicad.

    Some pictures compare the first PCB vs the second iteration

    The second iteration has some differences in pin configuration as well as minor change in circuitry:

    • Different pins for driving the LCD0802
    • Employ AMS1117 as post filtering, improve temperature reading. First iteration use mini360 directly without post filtering
    • Use a simple LED (forward bias) instead of zenner diode (reverse bias) to clamp voltage at input of LMV321
    • Use 0805 thermistor just to measure temperature inside the case, no need to measure temperature at the MOSFET

    Differences in docking sensors

    The left one used TCRT5000 in a module for line tracking sensor, as my first experiment. It was basically 2 LEDs, IR diode and IR phototransistor put inside a small plastic case. It works by detecting reflecting IR beam from a white-ish surface for a HIGH level output, while dark surface reflect much less IR beam for a LOW level output.

    But of course it only works with bright color handle! Yeah nah ~_~.

    Then I did some experiments with this reflection method. It never worked correctly. Then I changed the sensor design to interrupting instead of reflecting and it worked much more reliable.

    If you read to this point, congratulation! You are just bored and have nothing to do 😀

    And you probably wonder why would I want 2 soldering stations? The answer is: desodering SMD stuffs using 2 soldering irons, like 0805 or similar size. I just need to heat up 2 ends of the part and pick it up just like a tweezer.

    Hotair station is handy for SMD, but sometime this tweezer trick is a lot faster and safer especially working with PCB that has plastic connector such as JST XH. You simply don’t want to deform the connector, yeah?

    So yeah, after 8 years the very first soldering station I made still runs just fine. The cost of each station is about AU$60. Compare to Hakko FX951 ~AU$400 of the same function, it’s not bad at all.

    If you want the stand design by any chances, you can go to my github https://github.com/ceezblog/T12-Solder-station and look the the STL file in folder Enclosure

  • Writing windows app to communicate with UART Transparent BLE module – C# winform

    Writing windows app to communicate with UART Transparent BLE module – C# winform

    This post is about how to write a C# winform app to connect to your project using BLE module. This post is from point of view of an electronic engineer instead of software engineer and just focus on the BLE communication programming part only, especially for those UART transparent BLE modules, like RN4871. There is no complete sample in this post but just some hints and code snippet here and there to help with programming.

    Introduction

    Imagine, you write a windows app and you need to collect data from your microcontroller project, then pretty much you need to find a way to link them together, yeah? There are obvious choices:

    • USB to UART dongle and wire it up directly to UART port on your microcontroller
    • Implement wifi and IoT (internet of thing) software stack on your project and communicate with your host app via TCP-IP or so, like ESP8266 or ESP32
    • Use some transceiver modules and convert UART into RF, like RF24L01, CC1101, or just simply bit-bang data over a pair of 315 MHz transceiver modules
    • Use Bluetooth (known as BT classic) over virtual COM port, by using some module like HC-05
    • Or just use Bluetooth LE (BLE or bluetooth low energy)

    Below is the pictures that I stole from the internet. And yes, I have tried them all.

    If we wound back to 20-30 years ago, the obvious choice is BT classic and virtual COM port for a commercial product. Unless your project needs to dump a huge chunk of data without any loss, then you should choose TCP-IP over ethernet cable or wifi instead. But now, BLE is so popular these days, why not choosing BLE instead, yeah?

    Before I wrote SVI-Toolset app, I really struggled with researching BLE communication programming on windows PC: there was almost no windows app, nor there was a solid sample code for windows PC to connect to a BLE module. I am talking about off-the-shelf module like RN4871, CC2541… The only really working sample I could find was BLE console. It was around Feb 2023 when I started experiment with BLE and untill today, Aug 2025, I still couldn’t find a working sample from M$.

    Of course, there are some paid framework to work with BLE, which framework also compatible across muliple platforms, like Windows, MacOS, Linux… But for some small fry like me, it’s not possible to invest in a large sum of money and time for that.

    Okay, enough ranting, let’s dig into the good stuff!

    Prepare visual studio project

    You might need newest visual studio. At this time of writing, VS Community 2022 is free. Make sure you install WinUI application development (or windows ux for older visual studio).

    Just start a new winform project as usual, nothing special here. The trick is to add reference to windows ux library that provides headers for Bluetooth or Bluetooth Low Energy. Go to project list panel, add reference and browse to the header file:

    C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.22000.0\Windows.winmd

    If you can’t find this folder, then just download and install Windows SDK or choose a newer version that you have. I still use old version just fine, which version was for windows 10, instead of using newest version 10.0.26100

    You actually can use VS2013 and install Windows SDK for the same header instead of using VS2022.

    If you double-click on the the assembly name you will see all of the supported classes that this header provides. Among tons of classes this windows ux supports, we are interested in:

    So, just add a winform dialog maybe, and a couple of labels, buttons… Then <view code> of your “form1.cs” and add those classes to your code.

    Sorry if I am mixing terms from C++ and C#, I code embedded C more often than winform C#.

    C#
    using Windows.Devices.Bluetooth;using Windows.Devices.Bluetooth.Advertisement;using Windows.Devices.Bluetooth.GenericAttributeProfile;using Windows.Devices.Enumeration;using Windows.Devices.Radios;

    How BLE works? View from a different angle

    I’ll spare you the boring detail about BLE, GATT profile, GATT service… whatever. You can read it here https://www.bluetooth.com/. However, I am sure after you read/watch a sh!t load of those documents and videos about BLE, still you couldn’t write program to talk to a BLE module. But of course I have to include a tiny bit of info about this, just enough for you to write code.

    Okay, before I go on, I advice you to forget everything you know about BT classic and all definition of server/client or host/client you understand so far. I could have sworn those BLE definitions were made to brainf~ck with us. Just try not to compare BLE with everything you know, okay?

    Every communication should be 2-way, yeah? Unless your project is about just sending out data, like temperature sensor or an SOS beacon. So that you need send and receive which is 2-way communication between 2 devices. BLE stuff doesn’t provide a direct definition of send and receive like a conventional communication like UART or SPI. but for BLE, you have multiple GATT services (or GATT profiles) on a single device, but most of the time you have only 1 or 2 services. Each service may be for different purposes.

    You should only use your BLE device as BLE server and run the GATT services that your PC app can send request to. Below is what a BLE device should be like

    So each BLE differenciates to another by their name and their MAC address while GATT services and Characteristics are distinguish by their UUIDs. Those UUIDs above were mocked up, btw. Basically, it is just the same as company tag, division tag, and individual worker tag. You can define your own tags to BLE device as you like. Some of off-the-shelf BLE modules do allow you to change those tags, others don’t.

    Each characteristic could have multiple properties: read, write, notify… You don’t have to understand those properties. I can say each characteristic is a basket to hold the message so it can be 2-way or one-way. Normally we set UART-TX on one characteristic and UART-RX on another characteristic to eliminate confusion. Datasheet of the UART transparent BLE module will tell you exactly which characteristic is to send or receive of UART.

    For of RN4871 (page 65 RN4871 user’s guide)

    • Service UUID: 49535343-FE7D-4AE5-8FA9-9FAFD205E455
    • Characteristic UUID for UART-TX: 49535343-1E4D-4BD9-BA61-23C647249616
    • Characteristic UUID for UART-TX: 49535343-8841-43F4-A8D4-ECBE34729BB3

    For WCH CH9141 (page 6 WCH BLE-TPT.pdf)

    • Transparent UART service UUID: 0xFFF0
    • Characteristic UUID for UART-TX: 0xFFF1
    • Characteristic UUID for UART-TX: 0xFFF2

    Code from PC side

    The work flow of communication to BLE device from PC app

    You have to declare in your code a few objects to deal with the hierarchy structure of BLE.

    C#
    BluetoothLEDevice bleObj;GattDeviceService gattSer;GattCharacteristic gattRX;GattCharacteristic gattTX;

    Your PC app must manage the BLE devices on its own. It seems more work, but it’s actually better for the user. Think about the old way, user has to find the correct COM port to connect to. It’s more trouble if user plugs in many devices that pop up as COM port, such as Arduino Leonardo, CP2102, CH910F. Arduino Leonardo driver is the most troublesome, as it pops up as different COM port when plugged in different USB port. Additionally, most users are not tech savvy who can open device manager to read the COM port number.

    To simplify, You need to

    • Scan for nearby BLE
    • Select the correct BLE – store it’s Mac address for future use
    • Assign that BLE device to bleObj
    • Assign correct Gatt Service to gattSer
    • Assign correct Gatt Characteristics to corresponding gattRX and gattTX
    • Add event listener that monitors ValueChanged of gattRX
    • Use gattRX object to receive data
    • Use gattTX object to send data

    Easy peasy, lemon squezzy!

    Scan for BLE devices

    There are two class you can use for scanning nearby BLE devices: BluetoothLEAdvertisementWatcher and DeviceWatcher

    Ok, so BLE is totally completely different beast to BT classic. You DO NOT go to Bluetooth and devices to add a new BLE device. In fact, you don’t have to pair with BLE device using system. This is true for Windows and Android. I don’t know about iOS or linux though.

    The ble “connected” status is just a virtual concept, as ble device only wake up, send data, then go back to sleep. Mostly. There is no need for pair or sync. Of course, BLE is not for transmitting data securely, so keep in mind that you should obscure your data before sending over BLE, by encoding or encrypting the payload.

    You have to manage BLE device inside your code: scan, connect and disconnect! Yup, disconnect just makes killing the “virtual link” between PC app and BLE device quicker. There is a timeout before the BLE module decides to accept “new link” and broadcast its advertisement again, for RN4871 this is about 5s.

    Okay, both BluetoothLEAdvertisementWatcher and DeviceWatcher can be used for scanning BLE beacons to detect nearby BLE devices, they are practically doing the same thing.

    The only different is BluetoothLEAdvertisementWatcher only listen for BLE beacon or BLE advertisement messages. This can be super useful to filter out non BLE device nearby. While DeviceWatcher will monitor all nearby devices.

    Using BluetoothLEAdvertisementWatcher is super easy

    C#
    // short versionBluetoothLEAdvertisementWatcher ble_watcher;ble_watcher = new BluetoothLEAdvertisementWatcher();ble_watcher.Received += (BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) => { /* add new device to the list here */ };// run the watcher when you need to scan nearby ble devicesble_watcher.Start();

    Longer version of

    C#
    // a class to hold string data, similar to struct of C++class My_BLE_Device {	public string name;  public ulong address;  public short RSSI;  public My_BLE_Device(string device_name, ulong device_address, short my_RSSI){  	name = device_name;    address = device_address;    RSSI = my_RSSI;  }}List<My_BLE_Device> _ble_dev_list = new List<My_BLE_Device>();void scan() {	BluetoothLEAdvertisementWatcher ble_watcher;	ble_watcher = new BluetoothLEAdvertisementWatcher();	ble_watcher.Received += BLE_watcher_receive;		// run the watcher when you need to scan nearby ble devices	ble_watcher.Start();}// callback function when a new BLE device pops up in the scannerprivate void BLE_watcher_receive(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) {  if (args.Advertisement.LocalName.Contains("SV") || args.Advertisement.LocalName.Contains("BLE")) { // found device  var found_ble_dev = new My_BLE_Device(args.Advertisement.LocalName, args.BluetoothAddress, args.RawSignalStrengthInDBm);  // check in our list if not exist  if (_ble_dev_list.Count == 0 || !_ble_dev_list.Exists(x => x.address.Equals(found_ble_dev.address))) {  	_ble_dev_list.Add(found_ble_dev);    }  }}

    Just have to add event listenner to run a callback function when receive an advertisement beacon. You get RSSI (received signal strength indicator) coming along with ble advertisement beacon.

    As sample code above, I only choose to add ble devices which name contains “SV” or “BLE”.

    In the other hand, using DeviceWatcher is a little bit of dark magic involved

    Below is some code I took from BLE Console app

    C#
    List<DeviceInformation> _deviceList = new List<DeviceInformation>();string _aqsAllBLEDevices = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";string[] _requestedBLEProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.Bluetooth.Le.IsConnectable", };deviceWatcher = DeviceInformation.CreateWatcher(_aqsAllBLEDevices, _requestedBLEProperties, DeviceInformationKind.AssociationEndpoint);deviceWatcher.Updated += (_, __) => { }; // add an empty inline function for this event listenerdeviceWatcher.Added += (DeviceWatcher sender, DeviceInformation devInfo) => { if (_deviceList.FirstOrDefault(d => d.Id.Equals(devInfo.Id) || d.Name.Equals(devInfo.Name)) == null) _deviceList.Add(devInfo); };deviceWatcher.Start();

    There are a few voodoo stuffs to put into the initial constructor there, alright. You will get more detail about the device this way but you don’t have RSSI information. For my need, RSSI is more important than extra detail about a BLE device.

    Just a note: ESP32’s BLE stack does not play nice with BluetoothLEAdvertisementWatcher. Somehow ESP32 doesn’t advertise it’s ble name, it’s just blank! So, I suggest to use both if you are connect to ESP32 to fix this. If you plan to use ESP32 as a transparent UART passthrough for your project, I advise you not to go into this rabbit hole. Although ESP32 allows you to freely program ESP32 to do whatever you want it to do, but the lack of DMA stuffs of arduino framework, make it very difficult to do it correctly.

    Connect, Send and Receive data from BLE module

    So, you have a list of BLE candidates, you choose one to connect to and then you should check if the BLE device is the correct one.

    One way to do that is to match the UUIDs of the target BLE device with the UUIDs from the datasheet. The code blow is to check if the BLE device has the same UUIDs of TX and RX for RN4871 BLE module.

    C#
    async Task Connect_BLE(ulong dev_address){    // Try assign a BLE device using its address to bleObj    bleObj = await BluetoothLEDevice.FromBluetoothAddressAsync(dev_address).AsTask().TimeoutAfter(10000);    // Go through all of its available services    var result = await bleObj.GetGattServicesAsync(BluetoothCacheMode.Uncached);    if (result.Status == 0) { // status = 0 = no problem        bool found = false;        foreach (GattDeviceService ser in result.Services) { //search through services to get our target services            if (ser.Uuid.ToString().Equals("49535343-fe7d-4ae5-8fa9-9fafd205e455")) { // found our Gatt service for RN4871                gattSer = ser;                found = true;            }        }        if (!found) {    //if not found the correct gatt service            bleObj.Dispose(); //we got squat, so dispose of this object            return;        }				// we have alread found correct characteristic with the same unique id        var result2 = await gattSer.GetCharacteristicsAsync();        if (result2.Status == GattCommunicationStatus.Success && result2.Characteristics.Count>1) {            gattRX = result2.Characteristics[0];    // first characteristic should be RX            gattTX = result2.Characteristics[1];    // second characteristic should be TX            // check if UUIDs are match            if (!gattRX.Uuid.ToString().Equals("49535343-1e4d-4bd9-ba61-23c647249616") || !gattTX.Uuid.ToString().Equals("49535343-8841-43f4-a8d4-ecbe34729bb3")) {                bleObj.Dispose(); // no match --> dispose                return;            }        }        else {            bleObj.Dispose();            return;        }        // looking good, we got a solid connection        // Subcribe to value_changed on RX characteristic => callback Characteristic_ValueChanged()        var status = await gattRX.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);        if (status == GattCommunicationStatus.Success) {            gattRX.ValueChanged += Characteristic_ValueChanged;            Callback_DeviceConnected(); // run some routine after have a solid link to BLE        }        return; // Connect successfully     }        // handle error    tb_Stat.AppendText("\r\nTimeout - Connect fail.");}

    Once UUIDs are verified, you have a solid target to read and write data to. So just use gattRX object and gattTX object to send and receive data.

    To “connect” or establish a “link” to BLE module, you just write something to BLE device and wait for response. Like the sample below, I write a change of configuration and set it to notify on the receiving GATT characteristic.

    C#
    var status = await gattRX.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);if (status == GattCommunicationStatus.Success){    gattRX.ValueChanged += Characteristic_ValueChanged; // monitor if value of this characteristic changed    isConnect = true;    Callback_DeviceConnected();}

    If success, then the app knows that BLE module is ready to reply to request of the app. Again, there is no definition of “connected” concept for BLE, I just make it up for more intuitive usage.

    On PC side, you will have send function like this

    C#
    // global declareGattCharacteristic gattTX;async Task SendData_BLE(byte[] data){    if (!isConnect) return;    var writer = new DataWriter();    writer.WriteBytes(data);    // WriteByte used for simplicity    await gattTX.WriteValueAsync(writer.DetachBuffer());}// in data preparation functionbyte[] data = new byte[5];data[0] = (byte)_BLE_MSG_ID.BM_REQUEST_BATTERY_VOLTAGE;data[1] = (byte)'0';data[2] = (byte)'0';data[3] = (byte)_BLE_MSG_ID.BM_SEPARATOR;_ = SendData_BLE(data); // assign an empty holder for this task

    So, you write some data to a GATT service, that assosiates with TX line of UART, and magically on the other end of BLE module, it spits out the same data on its UART port.

    Receive function is like this

    C#
    // global declareGattCharacteristic gattRX;// add event listener for when the value of that GATT service has changedgattRX.ValueChanged -= Characteristic_ValueChanged;// Callback funtion for event listenervoid Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args){    byte[] data;    CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out data);    ProcessData(data);}

    You add an event listener to GATT characteristic object and then when data poured in, the callback function will be invoked with the message from UART port.

    The problem is, one BLE message contains multiple bytes (characters) in one payload while each payload on UART line is just a single byte. A broastcasting interval must be introduced to send a bunch of bytes after some time, like 100ms for CH9141 and about 50ms for RN4871. Basically, after 50ms, BLE module just dumps all the data it currently holds to PC app.

    The maximum size of payload (or MTU, Maximum Transmission Unit) is dictated by the firmware of that BLE module. RN4871 fw1.1.8 only does 20 chars as payload while RN4871 fw1.3.0 can do 50 chars as payload. So that your message will be cut into 2 ble messages instead of 1.

    On the PC app, if you receive each payload and process each payload individually, you might have corrupted data.

    To overcome this, you can build yourself a custom protocol to recognize your data package with unique identifiers, like

    Message #1Message #2
    AAAAhello_to_my_friendsZZZZ

    AAAA marks the beginning of your data and ZZZZ marks the end of data. So that you just collect multiple messages continuously but only process those messages when you see both AAAA and ZZZZ in the data you collected.

    You can implement a fix-frame format like below for each BLE payload, assuming each data field is a 16bit number

    C#
    // 4 data fields separated by comma in a single ble payload[data_field_1],[data_field_2],[data_field_3],[data_field_4]// expand it into individual byte[byte1][byte2][,][byte4][byte5][,][byte7][byte8]...

    You can use your own creativity to make a suitable frame format for you.

    For ESP32, it is possible to change the MTU from default 23 bytes to a higher number such as 500 bytes. Which indeed will give you more flexibility to frame your data.

    Anyhow, I had a few bad experience with ESP32, both hardware and software. It still leaves bad taste in my mouth after about 5 years already. So that I don’t recommend using ESP32 for something that needs to be reliable and long lasting.

    Additional security stuff on the BLE device side

    Most of the UART transparent BLE modules don’t have a bluetooth profile, which auto “connects” to last paired device and does not allow new device to pair with, like bluetooth HID or bluetooth a2dp… Any client (your pc app from different PC) can connect to it at will without any pin code or so.

    Basically, if only you have the app and only you have the BLE device, then there is not a possibility someone tamper your device over BLE connection. But if you use this on a commercial product, this could be very bad. Imagine that you can freely pair with your neighbour BT speaker and you play heavy rock music at 2AM in the morning. Yeah, that problem.

    The PIN code paring of BLE stack is quite finicky and doesn’t work. So that you should implement software password check for yourself:

    • The device still allows connection but for a few seconds, just like you have 60s to disable house alarm after you unlock front door
    • Sustain a connection only after client sends correct pin code
    • Disconnect if the client doesn’t send correct pin code after a few tries
    • Reject connection after a delay, sending a disconnect notification. The time delay would deter brute force attack

    You should manage these code in your application microcontroller instead, which microcontroller that BLE module wires to via UART port.

    You can reject connection by reset the BLE module (pull RST pin to ground), disrupt power to the BLE module or even “enter programming” and issue a disconnect command manually.

    If your product is used or is going to be used by a large number users you should take security seriously.

    In summarise

    This post is not a tutorial for you to write C# code to connect to your BLE. It’s just some pointers and a few code snipet here and there.

    1. Choose a suitable BLE module as transparent UART passthrough.
    2. Read datasheet for its UUIDs for connecting to.
    3. Prepare Visual Studio project with reference to Windows UX package that provides Bluetooth relate classes
    4. Write your winform app that can
      • Manage the list of available BLE devices
      • Check if the select device is the correct target
      • Send BLE messages
      • Receive BLE messages
    5. Write pin code feature in your application microcontroller

    And that’s that!

  • Hello world, again and again!

    Start getting my blog running again. It’s painfully slow. You know, it’s life.