stardance has been extended another month! the new deadline is october 31 :)

You are browsing as a guest. Sign up (or log in) to start making projects!

james-lian

@james-lian

Joined June 8th, 2026

  • 34Devlogs
  • 7Projects
  • 3Ships
  • 18Votes
Amateur software dev + aspiring hardware dev
Open comments for this post

8h 7m 21s logged

Devlog #13: UI/UX Implementation (WIP)

With the backend functionality (mostly) complete, I’ve been working on implementing a smooth and easy-to-use user interface.


Added system tray icon

I added a system tray icon to allow the user to easily relaunch the UI while the backend continuously runs in the background.

Cloud-folder assignment combobox + two-stage assignment system

Instead of one-shotting everything, there’s now a two-stage system to cloud assignment. When a user adds a cloud to a folder, it first is added to UserAssignedClouds. During the next synchronization, the service will:

  1. check UserAssignedClouds
  2. upload the folder to the remote locations in those clouds
  3. move the clouds from UserAssignedClouds to AssignedClouds (this way, we know where the folder currently exists at)

Add an IgnoreRules editor for WatchedFolders

I had previously coded an ignore rules popup with a visual and raw rules editor, and I implemented it today specifically for WatchedFolders. The user can now set ignore rule globs (according to VS Code glob rules: https://code.visualstudio.com/docs/editor/glob-patterns) to give themselves more control over what the service syncs and what it doesn’t.


Miscellaneous UI improvements + functionalities

  • completed folders.tsx page
  • added web hook listener in WebView2 to natively open File Explorer for folders
  • redid the styling for the sync.tsx page (still a WIP)

Fix Dropbox/OneDrive OAuth triggers and revocations

There were previously issues with the Dropbox (wrong redirectURI) and OneDrive (wrong OAuth consumer link) flows that I fixed after some testing. Everything regarding different accounts should work 100%!

0
0
16
Open comments for this post

10h 12m 36s logged

Devlog #12: Architectural Cleanup

Dropbox and OneDrive providers are complete

I reused the same overall logic for the Google Drive provider to write code for the Dropbox and OneDrive providers. I’m using the .NET REST APIs for both to handle file chunking and uploads automatically.


Implement PKCE for all provider OAuth2 flows for better security

To enhance the security of the OAuth2 process from a local desktop app like mine, I’ve implemented the PKCE security extension for all providers, to prevent authorization code interception and injection.


Resolve Architectural Drift

Over the development of this app, I’ve been continually implementing new ideas and removing what hasn’t worked. Eventually, it got to a point where I had thousands of lines of redundant or mismatched code for the sync engine, redundant features laying in my scripts that were no longer planned or used, and many redundant properties for the database schemas as well. Overall I was losing track of my original vision and goals, and development was becoming increasingly slow and stressful.

To help me get out of this development rut, I turned to AI. Specifically, here was a portion of my prompt to Github Copilot:

Please do the following:
1. Analyze my code to figure out the current architecture
2. Identify: 
   a) areas of weakness
   b) inconsistencies
   c) what doesn't work, dead code, etc.
3. Ask me necessary questions needed to clear up any design (or for us to figure out the system architecture together)

Finally, only help me fix any issues in the code once we have a clear idea of everything (and I give you the go ahead).

Over the next day, I went over each of the identified errors or weaknesses one-by-one with the AI. It helped me pinpoint exactly the areas of my code that needed work, and I used it to successfully reintegrated the code with my original vision.

Note: I use ‘cloud’ to refer to a provider-account pair. For example, a Google Drive provider with a specific email is one cloud. A different cloud would be the same provider but a different email, or a different provider altogether.

Here’s a breakdown of what I did:

  1. Remove architectural drift, abandoned code, and unused features (e.g. GlobalIgnoreRules, automatic destination selection, FileSyncRecord and FolderSyncRecord)
  2. Implement a new-cloud bootstrap (when the user uploads stuff to a new cloud, perform a bootstrap operation to create all existing files and folders)
  3. Implement destructive cloud removal for when the user removes a cloud assignment
  4. Add pre-sync validation (reject missing local roots, reject invalid assignments)
  5. Make Watcher queuing reliable (detect unmonitored changes, etc.)
0
0
12
Open comments for this post

10h 7m 35s logged

Devlog #11: Cloud Provider File Actions 2 (WIP)

Google Drive Cloud Provider Actions Complete

Since the last devlog, I’ve successfully completed the logic required to sync files and folders to Google Drive accounts using the Google Drive .NET REST API.
Using the .NET REST API involves first building a drive client service object, through which you’ll use to interact with the REST API for actions like uploading files or deleting them.
The logic is written to keep an exact carbon copy of the local version of any watched folders on the cloud. It does so by storing a queue of all of the changes a user makes to a folder and its children, and automatically ignores any conflicting changes from FileSystemWatcher that could be simplified (e.g. if a user changes an item before deleting it, we can just delete it instead of first updating the changed file AND THEN deleting it).
As the REST API does not support uploading multiple items or entire folders, every file is uploaded one-by-one via a stream. All cloud items are tracked not by their cloud IDs, but rather through their hierarchical relationship with the overarching folder parent (WatchedFolder).

Google OAuth2 Flows

Since the drive client service is created via access tokens (which expire), there’s a very real possibility of our cached access token expiring in the middle of an operation. Thus, I decided to use the Google OAuth2 Flows library to automatically refresh the access token when it expires.


Microsoft Graph and Dropbox File APIs (WIP)

The Microsoft Graph and Dropbox File Action APIs should follow mostly the same high-level structure as before, with minor changes made to accommodate differences in how each of the APIs work.

Microsoft OAuth2 access token expiry handling

With the Microsoft API, I instead implemented a custom class TokenManagerAccessTokenProvider which inherits from IAccessTokenprovider, before passing this into building the GraphServiceClient.

0
0
13
Ship Pending review

I made a small hobbyist solder fume extractor complete with an air quality sensor for soldering applications.

Features include:

  • powered by a 12V DC power supply
  • 0.91inch OLED SSD1306
  • LM2596-3.3 buck converter for 3V3 supply
  • ENS160+AHT21 sensor to detect VOCs and eCO2
  • ESP-01S microcontroller
  • custom 3d-printed casing
  • 12V desk fan which pulls solder fumes through an activated carbon filter
  • flip flop switch circuit + MOSFET to control the fan, flyback diode to prevent MOSFET damage

A challenge that pushed the project’s completion date back was a manufacturing error that occurred on my PCB. The specific buck converter I ordered from JLCPCB assembly was the LM2596-3.3, but the first batch that was sent to me had the LM2596-ADJ, which output the default ~1.3V instead of 3V3. I only found out about this after soldering on both PCB boards, which meant I had wasted time, effort, and some good usable components which I failed to desolder.

  • 7 devlogs
  • 21h build
Video of Project → See source code →
Open comments for this post

8h 26m 30s logged

Devlog #10: Cloud Provider File Actions (WIP)

Currently working on the cloud provider services API of my backend, making file actions such as uploading files from the local filesystem to a cloud provider (Google Drive, OneDrive, Dropbox) possible.

Cloud Provider Services (WIP)

I recently just implemented the OAuth process for Microsoft Entra/Graph in order to access the Microsoft OneDrive API, which differs quite significantly from the Google Cloud OAuth process for Google Drive API, despite the fact that both services use the OAuth2.0 protocol. For one, Google uses centralized endpoints and the unified Cloud dashboard to manage desktop apps that require access to Google user information. Microsoft uses their identity platform (Microsoft Entra UID) with endpoints that can be multi- or single-tenant. The actual scopes where the REST APIs for Microsoft OneDrive and other services resides in Microsoft Graph.


Implemented Glob Processing + Ignore Rules for FileSystemWatcher

In my application, the user can implement ignore rules for specific folders, allowing the application to prevent any matching files or folders from being monitored and uploaded to the cloud. The specific glob rules I referenced come from the VS Code docs: https://code.visualstudio.com/docs/editor/glob-patterns

Essentially, I have a collection of methods that parse the glob and turn it into a regex, which I can confirm is a match with a specific path through Regex.IsMatch().


0
0
53
Open comments for this post

8h 29m 51s logged

Devlog #9: FileSystem Events

I’ve been spending some time reworking and polishing the logic and database schemas that allows the OS to monitor changes made to WatchedFolders through the FileSystemWatcher class.

Handling FileSystem Events

Here’s the current rundown of how my program monitors for filesystem events (e.g. file created, deleted, renamed, moved, or changed)

filesystem event
    -> record the change in a database queue
    -> return

When a scheduled backup runs, the program reads the queue and the associated changes made to each file/folder, and then updates the corresponding item in the cloud.

scheduled backup
    -> read queued changes
    -> check whether the file still exists
    -> wait/retry if it is locked
    -> upload the latest version
    -> remove the queue item/mark as complete

I’ve also made edits to the OnFileRenamedEvent() to differentiate between renames and moves, which both use the “Renamed” event.


PendingSyncItem

This will be a SQLite table that stores pending sync actions upon being handled by the event handler.

The PendingSyncItem works in a simple way for both files and folders. It tracks five types of boolean events props in terms of cascading importance. Multiple event props can be true.

  1. Deleted - the most important event prop. If this prop is true, all other event props are skipped (as they matter not if the file was deleted). A deleted API request will be sent to the corresponding REST API.
  2. Moved - creates the file if the file did not previously exist. References the string OriginalLocalPath.
  3. Renamed - sends a rename API request.
  4. Changed - finds the existing file in the cloud and updates the file.
  5. Created - creates a new file. If a file was Created, Deleted, and then Created, two PendingSyncItems will be created. The first one will be overlooked as the Deleted prop is true.

0
0
14
Open comments for this post

5h 32m 30s logged

Devlog #6: Getting throttled

Attempting to complete my PCBs, and I’m going through it, fr.

Fried transistors + attempted fixes

After receiving my PCBs, I got to work to solder my components. I had ordered PCBA for any components I didn’t have, such as inductors, the barrel jack connection, etc., but I was planning to save money by hand-soldering components I DID already have, such as transistors and my entire MCU system. This also gave me the added benefit of testing these components beforehand, and it allowed me to go through several iterations of code on the ESP-01S.

The first issue I encountered with hand-soldering was with soldering the tiny, microscopic transistor holes (it was my first time doing so). The copper pads for my PN2222 breadboard transistors are super close together, and with my shaky hands, I accidentally whiffed, put too much solder, and left a solder connection between all three pads. Whoops!

I eventually managed to fix it with a solder sucker.
But after soldering, I decided to test the transistors with a multimeter to check if they were damaged, and both transistors were duds. I probably spent too long trying to fix it with the soldering iron and ended up frying them both.

After this, I attempted to salvage the project by desoldering some of the more expensive components from the board and using it on the second backup PCBA I ordered from JLCPCB. I managed to salvage the OLED screen, but while desoldering the ESP-01S, I broke off several pins, essentially leaving the chip unusable. I decided not to salvage the bigger chips (e.g. ESP-01S, ENS160+AHT21), and decided to just reorder some components instead for the second attempt.


New attempt: flip-flop switch fix + MCU not working

I managed to solder all components to the second PCB cleanly, without any hiccups. However, on trying my project out, I encountered two issues:

  1. The flip flop switch turns on the fan, but it doesn’t turn it off.
  2. The 3.3V MCU system (including the OLED, ESP-01S, and ENS160+AHT21 sensor) doesn’t seem to turn on.

The first issue was relatively easy to fix. Turns out, I had forgotten to add a pull-up resistor near the bottom of the flip-flop switch, and thus my steering capacitors had no path to VCC to “refill” (reset) their charge after the initial button press. I quickly soldered a pull-up resistor to VCC to fix it.

The second issue stumped me for a while. I found out that I had forgotten to connect pin 4 (EN) of the ESP-01S to 3V3, so I quickly soldered a connection, but it still wasn’t working. Eventually, I took a look with my multimeter and found the problem: my 3V3 logic chips were actually running on 1.23V for some reason.

It turns out, JLCPCB had sent me PCBA’s with the WRONG BUCK CONVERTER. I had ordered an TDSEMIC LM2596S-3.3, which would normally work perfectly, but JLCPCB had sent me PCBA’s with TDSEMIC LM2596S-ADJ instead.

I’ll have to either contact JLCPCB for a refund, or attempt to salvage the PCB myself by modding it. I’ve burnt enough allowance on this ;-;


Below: desoldering fail, attempted fixes (2), close-up of LM2596S-ADJ vs what I actually ordered (https://www.lcsc.com/product-detail/C42421958.html?s_z=n_q_C42421958&globalKeyword=C42421958)

0
0
43
Open comments for this post

9h 6m 45s logged

Devlog #8

Database schemas + API planning

I’ve mostly been working on database schemas and models since the last devlog, and I’ve created APIs to allow the frontend to access this information from the database as well. A lot of systems design work has been going on in the background. As I’m still a beginner to such concepts, a lot of rewriting code has also been going on in the background!

To be honest, progress has slowed since I’ve been splitting myself between working on the frontend and backend. I often find myself getting a bit stuck or lost in what’s happening. I think I’ll work on completely finishing one thing before moving onto the next for the next devlog.

Below are snippets of what I’ve been working on, including API methods for the frontend, the API endpoints for the backend, and some peeks at some of the changes I’ve made to existing models/new models that I’ve created.


FileSystemWatcher

I’ll be using the built-in C# FileSystemWatcher in order to track which files and folders the user has made changes to. The alternative is polling for these changes on-demand (e.g. checking the folder’s “Last-Modified” date). However, the problem with such an approach is that Windows does not automatically update a folder’s “Last Modified” date exactly when a file inside that folder is changed. Thus, to confirm whether any changes actually occurred to a folder or not, the program would have to poll ALL subdirectories and files, which could be performance-intensive. On the other hand, FileSystemWatcher is an abstraction over OS-level kernel APIs. The kernel itself registers a listener on an attached folder when FileSystemWatcher is used. Thus it is very efficient and much more accurate than polling a folder’s properties.

0
0
12
Open comments for this post

2h 44m 1s logged

Devlog #3

Reconfigured PCB to use surface-mount 74HC595

As the teeth of a through-hole 74HC595 would block the sliding action of the stator, I took some time to redesign the PCB with a surface mount component instead.


3D CAD casing

I also spent some time designing the casing that would hold everything together. It’s a little bulky, but I think it’ll work for now. I will be adding finishing touches (including mounting holes) later.


1
0
40
Open comments for this post

48m 38s logged

Devlog #2

Almost completed PCB Design for slider + stator (design error)

I redesigned my PCB several times to achieve the optimal layout for all the components. I ran into several issues along the way.

1) Rx electrode and Tx transmitter positioning

The Rx electrode and Tx transmitter array are sensitive capacitive devices. When positioned close to other electrical components, parasitic capacitance can be introduced, which can disrupt our weak signal and create crosstalk. It may also affect the integrity of our 8-phase signal, causing phase delays and shifts.

2) Connecting the Tx array pads to the 74HC595

Wiring the Tx array pads (under the Rx electrode) in a way so that every 8th pad was connected was a nightmare. I couldn’t directly draw traces over the copper pads as that would disrupt the ground plane, which is crucial for blocking noise from other components and external sources. For example, the human body is essentially a giant capacitor, and a human placing their hand over an unshielded Tx array would completely ruin our delicate signal. Therefore, it is crucial that the copper traces avoid cutting up as much of the ground plane as possible.

Thus, I ended up reducing the width of both the Rx electrode and Tx array to make extra room for running the copper traces to the side. I also added a small grounded copper pad between the traces and the Rx electrode just to shield it from any parasitic capacitance.

3) Design mistake

I realized just now after looking at the PCBs I ordered that I made a tiny mistake with my op-amp circuit. To the left of my Seeed MCU, I have an op-amp functioning as an ultra-high input impedance buffer. I’ve also attached an image of the schematic.

An op-amp normally takes a weak voltage signal, compares its two inputs, and multiplies the difference to create a much stronger output voltage. On my circuit, Pin 4 and Pin 1 (VOUT) of my circuit are connected to create a 1:1 voltage ratio, creating a unity-gain buffer feedback loop, so no amplification is actually happening. What DOES happen is that we’ve exposed the op-amp’s high input impedance, meaning it draws incredibly little current from our incredibly weak Rx electrode capacitor. This prevents our Rx electrode from just straight discharging our AC signal our circuit - preserving its original timing and phases. We feed the signal into pin 3 of the op-amp, allowing it to ride the 1.65 V (introducing micro-fluctuations, e.g. 1.64V-1.66V), and our op-amp outputs this signal directly to our MCU pin.

My mistake was that I connected the Rx electrode straight to pin 4 instead of pin 3.
I will see if I can fix it with some quick mods. Fingers crossed.


0
0
35
Open comments for this post

5h 19m 57s logged

Devlog #5

CAD Modeling

With the code and PCB done, I needed a frame to hold all of the hardware together. Therefore, I spent a couple hours designing two cases in Fusion360 - one to house the PCB, and one to house the fan and the carbon filter I was using.

PCB Case

This PCB case is a little different than the ones I’ve designed so far. For one, the MOSFET and the capacitor that arrived on the PCB assembly I ordered from JLCPCB were much larger than I’d imagined, so I needed to design holes in my casing to accommodate for their size. Additionally, the ENS160 + AHT21 sensor needs generous access to air in order to provide accurate readings. I couldn’t encase them in the casing entirely, so I needed several unusual holes in my design so as not to interfere with the placements on my PCB board.

I ended up designing a snap-fit housing with two parts - top and bottom (my first time designing a snap-fit joint!). The top part has several holes to ensure enough spacing for PCB components such as the OLED screen and keyswitch. It’s not air tight, but I really only need it to keep the PCB isolated and make things more presentable.

Fan Holder Case

I also needed to model a CAD case for the desktop fan to hold it and the active carbon filter in place. The fan case itself is actually a very simple design - three separate “panels” held in place with 4 screws and heat inserts.

0
0
56
Open comments for this post

3h 12m 22s logged

Devlog #4

Finalizing MCU program

I used the breadboard prototype I had from Devlog #1 to finalize a working version of the ENS160 + AHT21 sensor program on the ESP-01S. I use the Adafruit libraries for these components to extract their readings, and then display it on a scrolling background on my 0.91 inch OLED SSD1306.


This program is the final step before I solder the remaining components onto my PCB, which conveniently arrived as I was on vacation.

It displays the following information:
Line 1: Temp (deg C), Temp (deg F), Humidity (relative %)
Line 2: Air Quality Index (AQI), Total Volatile Organic Compounds (ppb - TVOC), eCO2 (ppm - estimated CO2)

0
0
52
Open comments for this post

9h 31m 6s logged

Devlog #7

API errors fixed - OAuth now works 100%

In my specific architecture, API calls from frontend to backend work like so:

fetch() call from Vite frontend --> intercepted by WPF WebService2 middleware shell --> forwards down NamedPipe --> received by Kestrel (cross-platform web server) on the ASP.NET Core C# backend. 

For the longest time, I just couldn’t get API calls from the frontend to reach the backend. I would always end up with a TypeError: failed to fetch(), which, as I found out, could mean a lot of things. Initially, I thought it was a CORS issue with the backend. I added builder.Services.AddCors() to allow cross-origin requests, and also adjusted the content-headers included with the fetch() request on the frontend, but I still experienced the same issue.

Eventually, I discovered that it was a CORS issue with the MIDDLEWARE. It turns out after the WPF WebView2 shell forwards the fetch() request down the NamedPipe and retrieves a response from the backend, it actually creates a new web request to return it to the frontend with createWebResourceRequest(). This new response DOESN’T COPY the CORS headers from the backend, meaning the Access-Control-Allow-Origin header was missing from the response despite the backend returning a 200 OK status code. This blocks the frontend from reading the response, and a generic TypeError: Failed to fetch is thrown.

Since the desktop app is essentially a proxy/interceptor - it’s creating its own HTTP response, so it needs to handle CORS headers itself.

I was also missing an OPTIONS preflight responder on the backend, so I added that in as well.


Listener callback dynamic port access

When OAuth is requested from the frontend to the backend, the backend adds a listener to the provider’s OAuth function call. This way, say when the user completes Google’s OAuth process, Google redirects to our listener url, and we can display a Success page to the user.

I had a minor logic error in my listener callback implementation where the same port was always used, even if it was unavailable. For this reason, if the user were to leave the OAuth page and retry the OAuth process, they would encounter an error if the listener was still not cleaned up. I implemented a dynamic port searcher to automatically serve the user a port that was free and unused.


Implementing EF Core Backend System and FileSystem schemas

I’m currently planning to implement the core of the backend functionality, which is to monitor file changes occuring to “watched” folders and upload them asynchronously and autonomously to providers the user has signed into.

A great way I’ve found to map out future app functionality is to map out schemas for anything that needs to be stored locally. Currently, I’m working on schemas for SystemSettings, SyncEvents, and FileSystemWatcherWorker.


Below is a short overview of the current basic features of the app, including OAuth account management + a notification/error-logging system.

0
0
7
Ship

I made a custom 3x4 layout macropad configured with 5 keyboard profiles! It also features a unique keylogging mechanism using a custom class KMK extension that places itself within the KMK firmware Display update loop to display to the user their inputs and key combinations.
Everything is my own design, including the PCB and 3d printed CAD parts. Made with parts from the hack club hackpad kit.

The keyboard profiles include:

  1. Default (includes commonly-used keys like HOME, END, arrow keys, and some Fn keys)
  2. Numpad (includes numbers)
  3. Art layer (includes common Krita hotkeys like B for brush, E for eraser, A, S, R, etc.), activated through the Toggle key
  4. Art-CTRL layer (includes common CTRL-related commands for Krita, including CTRL+A and CTRL+S), activated by holding the bottom-left modifier key on the Art layer
  5. Art-CTRL-SHIFT layer (includes common CTRL-SHIFT-related commands for Krita, including CTRL+SHIFT+A and CTRL+SHIFT+Z), activated by holding the bottom-left modifier key AND the key directly above it on the Art layer
  • 7 devlogs
  • 14h build
  • 5.00x multiplier
  • 69 Stardust
  • Hackpad
Video of Project → See source code →
Open comments for this post

5h 9m 12s logged

Final Devlog (build phase)

PCB soldering + Assembly

I’ve successfully soldered my PCB and assembled my parts using some 3d prints and the kit that arrived a while ago.
There was an issue with my design where 2 out of the 6 screws in my design ended up being redundant, but it still works as-is!


KMK fixes + added input display functionality

I tested the KMK code that I wrote earlier and due to several errors in the code, it didn’t work.

I refactored the code to fix the errors, and I also added additional functionality:

  1. Expanded the number of profiles (KMK layers) from 2 to 5
  2. Added modifier key-like functionality (e.g. Ctrl and Shift) using the said profiles
  3. Added a custom input display class for the OLED that logs the user’s keypresses and displays the following action!

Input display

The OLED screen features an input display that keylogs the user the key commands and combinations that they press.
Making it work was actually surprisingly hard. For one, KMK does not provide much built-in functionality to perform an action after a specific key is pressed. I decided to create my own custom class - OLEDManager - to manage the keyboard’s TextEntry’s. OLEDManager can be placed itself in the actual firmware loop of KMK by adding it to keyboard.modules - allowing it to activate after keys are pressed or after the matrix is scanned. However, this took a while for me to figure out - there was some boilerplate required that I only corrected after some trial and error.
Initially, the keylogging functionality didn’t work if multiple keys were pressed at once, as KMK did not store key press events in some sort of log. I made a workaround that tracked the keypresses by adding and removing them incrementally based on the keypress and keyrelease events. After that, I ran into the issue where some keys wouldn’t be logged at all, or would only show up on the display AFTER the keys were released. This took me a really long time to figure out, but any keys that aren’t associated with layer change keys (e.g. KC.MO and KC.TG) don’t prompt any KMK Display rerenders. I could not simply update the text property of the TextEntry objects, I had to actually force a rerender after every keypress in order for the logging functionality to perform as expected.

0
0
18
Open comments for this post

9h 43m 49s logged

Devlog #6

These past days I’ve been focused on cleaning up the auth code and setting up frontend-backend communication.

IScopeFactory - Singletons and Scoped Objects

.NET (modern C#) features native dependency injection, where abstractions can be mapped directly to a class, in order to allow that class to receive its required objects from an external source in a more efficient and cleaner manner.

In ASP.NET Core C#, you can register classes as a couple of different types of services to be injected into other classes. I mainly use singletons and scoped objects. Singletons, like their name suggests, are long-lived classes, of which a single instance exists throughout the application’s lifecycle. By contrast, scoped objects are created once per client request before being destroyed.

The problem is that when you use dependency injection, you cannot inject a shorter-lifespan class into a longer-lifespan class (e.g. a scoped into a singleton). This necessitates the use of IServiceScopeFactory to automatically manage the lifetimes of these child classes.


Thread-Safe Singletons

By default, the System.Collections.Generic library in C# is NOT threadsafe. That means that, if multiple scoped objects or singletons access the same Dictionary, that could throw an error. Thus, I switched them out for ConcurrentDictionaries throughout my app.


Multi-Account Functionality Integration

I further integrated functionality that allows my app to store MULTIPLE accounts per provider (e.g. 3 OneDrive accounts, 4 Google Drive accounts). This required refactoring the CloudToken object to include a new AccountId prop, retrieving that prop from the OAuth2.0 protocol, and rewriting some logic in TokenManager to include AccountId validity checks.


RevokeOAuthSync

I added a method to revoke OAuth by terminating the refresh and access tokens using the revoke OAuth2.0 endpoint.


Frontend-Backend Communication

With the basics of the auth protocol API and TokenManager API setup, I’ve begun attempting to facilitate API calls between the React Vite frontend and the ASP.NET Core C# backend. It’s still a WIP, but things are moving! I also added some additional UI to allow the user to select and manage their accounts on different providers.


0
0
4
Open comments for this post

9h 54m 42s logged

Devlog #5

I’m currently on vacation, but I’m trying to stay productive by working on some software projects.

OAuth2.0 Token Management + EF Core SQLite Setup + HTTPClient/DPAPI

The past few days, I’ve been working on setting up some basic OAuth functionality for Google and Google Drive. I thought it would be very simple at first, but perhaps since I’m new to C# and on vacation, it took me a bit to get used to the C#/ASP.NET features and development patterns, such as dependency injection.

Additionally, there’s plenty more to using OAuth 2.0 that I was not aware of at first. Step 1) involved acquiring ClientId and Clientsecret values from the Google Dev Console page. These were stored in User Secrets in Visual Studio along with the project.

For desktop clients, clients use the provider’s OAuth API link to apply for a long-lived Refresh Token. This Refresh Token, once encrypted and stored securely, is used to continuously request for short-lived Access Tokens, which is what is actually used for Google’s API services, like the Google Drive API.

Encryption was setup using the .NET data protection API, DPAPI. I created two classes: a GoogleDriveProvider to manage Google-specific OAuth setup and Drive API calls, and a generic TokenManager that handles the refresh-access token exchange for all OAuth-related providers, including ones I will add in the future such as OneDrive and Dropbox.

For the HTTPClients used to make the web requests, I decided to add an HTTPClient to GoogleDriveProvider using builder.Services.AddHttpClient().

Finally, all pertinent information to the app is stored within an SQLite database using EF Core. Refresh Tokens are stored within an object called CloudTokens.

I have the basic functionality for the OAuth setup, so my next steps will just be to test it out and debug for any errors.

Below are some snapshots from the code I wrote.


0
0
15
Open comments for this post

1h 42m 56s logged

Devlog #3 - PCB design

Once I had the design finalized from the schematic, it was straightforward enough to convert it into a PCB. I spent some time rearranging components to try and use as little space as possible.

I had to leave some extra space for the ENS160+AHT21 air quality sensor, as it doesn’t work without consistent exposure to air.

2
0
61
Open comments for this post

1h 37m 22s logged

Devlog #2 - schematic design

After confirming that the ESP-01S was a viable microcontroller for this project, I continued with designing the schematic for the PCB.


Power Management

ESP-01S is a 3.3V native controller, and the ENS160+AHT21 and the I2C OLED both use 3.3V logic. If I plug my 12V DC adapter directly into the ESP-01S, I’ll fry all my MCU components. Thus, I’m using an LM2596 buck converter to step-down some of the charge to 3.3V.


Air Quality Sensor + MCU

This section contains the sensor components and MCU. The air quality sensors and the OLED both share the same SCL/SDA I2C communication pins, which we established was possible in the previous devlog.


Flip-Flop Switch + MOSFET

Since the pins on the ESP-01S are being used, I decided to try exploring other options to turn my fan ON/OFF. I decided to use a flip-flop switch circuit. This allows me to persistently alter the fan’s ON/OFF state by simply clicking one pushbutton.

The flip-flop switch circuit is a bistable multivibrator. It has two stable states where it can output a persistent signal (either 0 or 1), thus why we say it can represent a single bit of information.

One of the states (transistors) is connected to a MOSFET (STP55NF06L), a heavy-duty voltage-controlled component which handles heavy loads much better than a simple transistor. The MOSFET helps control the fan.

I attached a flyback diode in parallel to the fan. Because a fan is fundamentally wire coiled around magnets (an inductor), when the flip-flop switch suddenly turns off, the magnetic field of the fan collapses, converting back into an extremely high-voltage electric signal that, if not protected against, could instantly fry my MOSFET. The flyback diode allows the current to loop back around through the inductor until it is harmlessly dissipated as heat.


Power-On Circuit

On its own, the flip-flop switch’s initial state is hard to control . It’s based on chance, and small manufacturing differences between identical resistors and capacitors used in the circuit.
To ensure the flip-flop switch always initializes to an OFF state, I added a tiny 10nF capacitor to one of the transistors. This makes one of the transistors slightly slower, thus ensuring the other one will always “win” the race condition.


Next, I’ll be designing the PCB and programming the ESP-01S!

0
0
12
Loading more…

Followers

Loading…