-
Notifications
You must be signed in to change notification settings - Fork 0
/
feed.xml
1494 lines (1235 loc) · 206 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-11-20T21:16:55-06:00</updated><id>/feed.xml</id><title type="html">CEKlopfenstein</title><subtitle>A place to show and post about personal projects and show examples of my work.</subtitle><entry><title type="html">Initial 3D Printer Escapades</title><link href="/weekly-posts/3d-printing/2024/11/20/3D-Printing.html" rel="alternate" type="text/html" title="Initial 3D Printer Escapades" /><published>2024-11-20T00:00:00-06:00</published><updated>2024-11-20T00:00:00-06:00</updated><id>/weekly-posts/3d-printing/2024/11/20/3D-Printing</id><content type="html" xml:base="/weekly-posts/3d-printing/2024/11/20/3D-Printing.html"><p>It’s a whole lot later than I had originally planned to post about this. But here we are. My initial escapades with 3D printing since High School.</p>
<blockquote>
<p>NOTE: This post has been in progress since June/July. With periodic work being done. Continuity errors may exist. Especially because the original plan was to post this the 15th of July. (Ouch this is late.)</p>
</blockquote>
<h2 id="but-why">But Why?</h2>
<p>Back when I was in High School, my school was fortunate enough to have been given funding to purchase and maintain a 3D Printer just before my Freshman Year. It was a <a href="https://www.seemecnc.com/products/rostock-max-complete-kit">SeeMeCNC Restock Max V2 3D Printer</a>, and I was fascinated. So, I took the classes that would eventually allow me to interact with it.</p>
<p>Eventually, I got permission from the teacher in charge of the printer (printers by this point) to print something I ended up making in Autodesk Inventor. Which at the time was the software we learned in one of his classes. So I took advantage of it. Until I was accused of burning through a kilogram of filament without saying anything. (It was later discovered that I had not. Another student bought a printer and didn’t have enough money for filament. So he simply stuck it in his backpack and walked out.)</p>
<p>Once it was all said and done, beyond the prints I did for the classes, I ended up with two puzzles. One being a Van Der Poel’s Puzzle, at least that’s what it was called on the solution. And a copy of another wooden puzzle I had previously been exposed to.</p>
<style>
@media (min-width: 500px){
#gallery0{
flex-flow: row wrap;
display: flex;
justify-content: space-evenly;
align-items: center;
}
#gallery0 > div{
width: calc(33% - 2.5px);
}
}
</style>
<div id="gallery0">
<link rel="stylesheet" href="/assets/image-box.css" />
<div class="image-box">
<div id="image0">
<img title="
Assembled Puzzles" src="
/assets/images/first-3D-printer/Both%20Puzzles.jpg" alt="
Assembled Puzzles" /><p>
Assembled Puzzles</p>
</div>
</div>
<div class="image-box">
<div id="image1">
<img title="
An example of a simpler puzzle disassembled
" src="
/assets/images/first-3D-printer/Pieces.jpg
" alt="
An example of a simpler puzzle disassembled
" /><p>
An example of a simpler puzzle disassembled
</p>
</div>
</div>
</div>
<div class="image-box">
<div id="image2">
<img title="The 'Solution' to the more complex puzzle. (Pink in the previous image.)" src="/assets/images/first-3D-printer/Van_der_Peols_Puzzle_SOLUTION.png" alt="The 'Solution' to the more complex puzzle. (Pink in the previous image.)" /><p>The 'Solution' to the more complex puzzle. (Pink in the previous image.)</p>
</div>
</div>
<p>Either way enough rambling. Since then I’ve wanted to buy myself a 3D printer. But at the time. I was a high school student who didn’t have the funds they could spare. As everything was being saved to pay for college. So that had to wait.</p>
<h2 id="choosing-a-printer">Choosing a Printer</h2>
<p>But that was then and this is now. And I have a bit of spending money. But deciding what printer was the next matter.</p>
<h3 id="requirements">Requirements</h3>
<p>Unfortunately, I had a few requirements. Many of which are a result of my current living situation. Which is relatively space-constrained. So right off the bat, I needed something small and compact.</p>
<p>Beyond that, my requirements were rather broad. Those being that it’s a decent volume, a cheap price, and wouldn’t be a complete piece of garbage. The logic being that I really don’t want to go all out just yet as I wasn’t entirely sure I would want to continue it as a hobby. I also didn’t want something that would require me to go out and buy 20 things in order for it to give me a simple print.</p>
<p>A couple of other things I wanted were features that didn’t exist on the printer I first used. Mostly being Automatic Bed Leveling. Just for the convenience.</p>
<h3 id="options">Options</h3>
<p>So with the requirements decided which one would I get? Well. I looked into quite a few actually with the list below detailing some I considered as well as notes.</p>
<table>
<thead>
<tr>
<th>Printer </th>
<th>Price </th>
<th>Bed Volume </th>
<th>“Desk” Space </th>
<th>Auto Bed Leveling </th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://all3dp.com/1/monoprice-mp-select-mini-3d-printer-review/">Monoprice Mini Select</a></td>
<td>$200</td>
<td>120mm x 120mm x 120mm</td>
<td>~430mm x ~360mm</td>
<td>❓ Depends (V2 Maybe?)</td>
</tr>
<tr>
<td><a href="https://www.tomshardware.com/reviews/monoprice-mini-delta-v2">Monoprice Mini Delta</a> </td>
<td>$179</td>
<td>r55mm x 120mm </td>
<td>~304.8mm x ~304.8mm</td>
<td>✔️ Yes</td>
</tr>
<tr>
<td><a href="https://www.tomshardware.com/reviews/bambu-lab-a1-mini">Bambu Lab A1 Mini</a> </td>
<td>$249 </td>
<td>180mm x 180mm x 180mm</td>
<td>347mm x 314mm</td>
<td>✔️ Yes (Plus More)</td>
</tr>
<tr>
<td><a href="https://www.tomshardware.com/3d-printing/creality-ender-3-v3-ke-review">Ender 3 V3 KE</a></td>
<td>$329</td>
<td>220mm x 220mm x 240mm</td>
<td>433mm x 366mm</td>
<td>✔️ Yes</td>
</tr>
<tr>
<td><a href="https://www.tomshardware.com/reviews/creality-ender-3-v3-se">Ender 3 V3 SE</a> </td>
<td>$249 </td>
<td>220mm x 220mm x 250mm</td>
<td>349mm x 364mm</td>
<td>✔️ Yes</td>
</tr>
<tr>
<td><a href="https://www.antonmansson.com/3d-printing-blog/the-final-creality-ender-3-v3">Ender 3 V3 CoreXZ</a></td>
<td>$349</td>
<td>220mm x 220mm x 250mm</td>
<td>349mm x 364mm</td>
<td>✔️ Yes</td>
</tr>
<tr>
<td><a href="https://www.pcmag.com/reviews/creality-ender-3-v2">Ender 3 V2</a></td>
<td>$209</td>
<td>220mm x 220mm x 250mm</td>
<td>475mm x 470mm </td>
<td>❌ No</td>
</tr>
<tr>
<td><a href="https://www.tomshardware.com/reviews/kingroon-kp3s">Kingroon KP3S 3.0</a></td>
<td>$179</td>
<td>180mm x 180mm x 180mm</td>
<td>390mm x 370mm</td>
<td>❌ No</td>
</tr>
</tbody>
</table>
<p>Table Notes:</p>
<ol>
<li>Desk Size found based on shipping size and presumed packing. Unless found otherwise posted.</li>
<li>If available Tom’s Hardware was used to find functionality/sizing. Please consult other sources as well when performing your research.</li>
</ol>
<h3 id="thought-process-on-selection">Thought Process on Selection</h3>
<p>If I boil it right down to what was most important for me it was the amount of deskspace it took up. But there’s another thing to consider when you’re looking at that. And that is most of the beds on these printers can move outside of the footprint they may take up. Meaning you not only need the space to have the thing sit. But also the space for it to move. And given my space constraints that only left the Mini Delta.</p>
<p>Another box it ticked for me was the auto bed leveling. And the fact that, at least according to my research, minimal servicing was needed to make it print decent things. I also was able to find it for $169 shipped before taxes. Making it the cheapest option that met most of my requirements.</p>
<p>Unfortunately the one requirement it only sort of meets is bed size. At a diameter of 110mm by 120mm it isn’t exactly large. And that’s a circle. If I wanted to print the largest square possible the max I can do is a little more than 77mm. But it fits most of my requirements. And while the bed space is small I think it will be workable for some smaller projects. Some more superglue will just have to be involved.</p>
<h2 id="modeling-software">Modeling Software</h2>
<p>With the printer chosen and ordered then began the wait. And the perfect time to start to figure out which software to use. And at this point the option I’ll be going with is FreeCAD.</p>
<blockquote>
<h3 id="addendem">Addendem</h3>
<p>Most of the below opinions and thoughts on FreeCAD is now hilariously out of date. Due to most of it being written mostly in June. And with the release of FreeCAD 1.0 may not be valid. Having not installed it yet. I can’t really say for sure though I do know that there is a default assembly workbench which is great.</p>
<p>Either way back to the original post.</p>
</blockquote>
<h3 id="free-what-a-great-price">Free. What a Great Price!</h3>
<p>There are a couple of reasons for this. And one of which is that it’s free. But that’s not the whole thing. So I originally learned CAD in high school as part of an “engineering” class. In that class, we used Autodesk Inventor. And I explicitly remember, even though it’s been a minute, that I planned to buy it when I could and had a printer. Sure it was expensive, and holy smokes was it, but I was familiar with it. And I thought it would be a one-time purchase.</p>
<p>Unfortunately the same year I had this thought happened to be the first year Autodesk stopped doing perpetual licenses. Meaning that if you wanted it you had to subscribe to it. And I’m not sure if they were the first but all the others seemed to start following suit. And while some of them have hobby levels you can rent for cheaper you are kind of at their whim regardless. So given I was going to have to learn a new piece of software I figured I’d go with the free and Open Source option.</p>
<p>And hey. As of writing this, they have even announced a feature freeze in order to prepare for the release of 1.0. Which is even better. I think I might even donate a bit at some point. We’ll see what happens.</p>
<h3 id="learning-software">Learning Software</h3>
<p>Now I’m no expert. And I’m not going to drone on about how to use FreeCAD. But I will say after learning the different naming conventions I’ve not run into much of an issue. That’s likely because I was never an advanced user of Inventor or any other CAD software but it works so far. I haven’t even run into the dreaded <a href="https://wiki.freecad.org/Topological_naming_problem">Topological naming problem</a>. However, that’s also something that’s expected to be fixed in 1.0. (Side note. The implementation for the fix to that problem has been activated in the 0.22 weekly builds for the week of June 19th. However, I am not using weekly builds at this point.)</p>
<p>To learn it at a basic level I decided to remodel one of the puzzles I printed in High School figuring they were both simple enough and complex enough for me to learn at least the basic features. And after a couple of hiccups. I did get it to work. I even got it so that I could change some dimensions using a spreadsheet.</p>
<div class="image-box">
<div id="image3">
<img title="Screenshot from FreeCAD with Puzzle Pieces and Assembly." src="/assets/images/first-3D-printer/Basic%20Puzzle%20In%20FreeCAD.png" alt="Screenshot from FreeCAD with Puzzle Pieces and Assembly." /><p>Screenshot from FreeCAD with Puzzle Pieces and Assembly.</p>
</div>
</div>
<p>There was one thing that initially surprised me. And that was the fact that FreeCAD itself does not have a way of creating assemblies by default. I had made use of Inventor Previously in order to make the solved model of the puzzle. In order to do that you need to install a plugin.</p>
<p>And there’s actually a couple to choose from. But not wanting to install plugins just yet. I ended up simply using transformations and rotations based on some predefined measurements to create the assembly. Doing this I ended up also making use of the clone feature. Allowing me to still change the original modeled pieces and see the effect on the solved puzzle.</p>
<h2 id="now-to-get-printing">Now To Get Printing</h2>
<p>Once I got the printer I ended up doing a single test print before proceeding. A “cat” ring that is on the SD card when you buy it. And it worked without anything screwy going on. So with that, I felt comfortable proceeding to the next step of my plan. Connecting the printer to Octoprint.</p>
<h3 id="octoprint-oh-the-power">Octoprint OH THE POWER!</h3>
<p>Now Octoprint is a very nice piece of software that allows you to control your printer remotely. More importantly, reducing the need to use sneaker net and that SD card in order to print things.</p>
<p>I thought it was going to be simple. I’d create a VM on Proxmox, pass through the Printer as a USB device, install Octoprint and I’d be off to the races. Unfortunately, that was not the case. Octoprint kept failing to connect.</p>
<p>Initially, in my search, I thought it was a common issue with Monoprice Printers and that being that the CH341 driver needed to be updated. Which is apparently a common issue. But after fighting with that due to a couple of different issues it still didn’t work. (<a href="#ch340ch341ch34x-driver-instructions">Instructions can be found here</a>)</p>
<p>And this is where it took the longest time. Unfortunately, it appears that the Monoprice Mini V2 specifically does some “nonstandard” (in comparison to most printers) serial handshake. Requiring you to modify Octoprint. And I only found this because WEEDO did this on their “own” printers. The WEEDO x40 also requires this modification. Unfortunately I never found instructions specifically for the Mini Delta V2. But the instructions for the X40 work. My copy of the instructions <a href="#modify-octoprint-to-function-with-monoprice-mini-delta-v2">can be found below</a>. Mostly because I do not fully trust them to not disappear into the wind.</p>
<p>But it took quite a bit of research to find that.</p>
<h3 id="first-prints-away---or-are-they">First Prints Away! - Or are they?</h3>
<p>With Octoprint set up, I began to do some printing as seen below.</p>
<style>
@media (min-width: 500px){
#gallery1{
flex-flow: row wrap;
display: flex;
justify-content: space-evenly;
align-items: center;
}
#gallery1 > div{
width: calc(33% - 2.5px);
}
}
</style>
<div id="gallery1">
<div class="image-box">
<div id="image4">
<img title="
Soldier Mini Test Print (In shadow)" src="
/assets/images/first-3D-printer/Soldier_Light_1.jpg" alt="
Soldier Mini Test Print (In shadow)" /><p>
Soldier Mini Test Print (In shadow)</p>
</div>
</div>
<div class="image-box">
<div id="image5">
<img title="
Soldier Mini Test Print (In more direct light)" src="
/assets/images/first-3D-printer/Soldier_Light_2.jpg" alt="
Soldier Mini Test Print (In more direct light)" /><p>
Soldier Mini Test Print (In more direct light)</p>
</div>
</div>
<div class="image-box">
<div id="image6">
<img title="
Cube Puzzle
" src="
/assets/images/first-3D-printer/Fresh_Cube_Puzzle.jpg
" alt="
Cube Puzzle
" /><p>
Cube Puzzle
</p>
</div>
</div>
</div>
<p>Unfortunately, though it wasn’t as simple as just hitting print. Which was to be expected. What is not pictured is the numerous failed prints and test prints. Some of which one of my cats has taken to stealing off my desk. But lucky enough I am not the first to be working with this printer. And while there seems to be a bigger community around the V1 the V2 has quite a few of the same issues.</p>
<p>My main problem for me is the fact that this community mostly seems to exist within a Facebook group. Which I can’t access because Meta has apparently determined I’m impersonating myself. And to protect myself from myself they deactivate any meta account I attempt to create. Luckily though someone was nice enough to create a complication of many of the more common issues and posted it <a href="https://www.reddit.com/r/mpminidelta/comments/bzm1s2/updated_mpmd_calibration_guide_and_faq/">within the subreddit</a> of all places. Somewhere I can access it.</p>
<p>And making use of these I was able to get some generally consistent results. Specifically, I ended up needing to create some clips that the FAQ/Help document recommended. Which fixed layer shifting. The only other major issues I’ve been having has been first-layer issues caused by a rather warped bed. Which I’ve been solving mostly by trial and error. A better solution would likely be to be using rafts. But I really don’t want to deal with removing those later at this point.</p>
<h2 id="final-notes">Final Notes</h2>
<p>Overall I’m happy with it. I’ve even bought another printer at the Midwest RepRap Festival <a href="https://www.facebook.com/midwestreprapfest/">(MRRF)</a>. Which I ended up fixing using the Delta to create a stop-gap measure for a snapped belt and a replacement end cap used to tension the very same belt.</p>
<p>Unfortunately, it appears that I may be using the second printer to fix the Delta now. How fitting.</p>
<p>With that said though here I will be including a list of links. Unfortunately, I didn’t keep the best notes for some of these. But I feel like these could be useful for others, or myself for that matter, that may happen onto this post later with the same printer. Either way. Thanks for reading and here they are.</p>
<h3 id="random-links">“Random” Links</h3>
<ul>
<li><a href="https://www.reddit.com/r/mpminidelta/comments/pbe9z4/mini_delta_v2_wont_connect_trough_usb/">Evidence of it failing to connect over USB (Reddit Post)</a></li>
<li><a href="https://www.reddit.com/r/MPSelectMiniOwners/comments/r7uaqz/mp_mini_v2_linux_trouble/">Evidence that connecting over USB should be possible (Reddit Post)</a></li>
<li>Calibration Roadmap
<ul>
<li><a href="https://drive.google.com/file/d/1RYnJSEOnuf3h62FwhQ2lCmzbucvMLhy7/view">Google Doc Link</a></li>
<li><a href="https://www.reddit.com/r/mpminidelta/comments/bzm1s2/updated_mpmd_calibration_guide_and_faq/">Calibration Roadmap Reddit post</a></li>
</ul>
</li>
<li><a href="https://www.reddit.com/r/mpminidelta/comments/wfgajm/mpmdv2_firmware_update/">Notice of Firmware Update (Reddit Post)</a></li>
<li><a href="https://gist.github.com/jaredharley/31e6a05da2a2edf44c5150d839439c9b">Notes on Web API that the Delta V2 has. (Github Gist)</a></li>
<li><a href="https://github.com/piberry/-MPMDv2-modifications-and-fixes">Mods/fixes (Github)</a></li>
<li><a href="https://www.dropbox.com/scl/fi/puv3chgfzmx7w3apppdco/MonopriceMiniDeltaV2FirmwareFiles.zip?rlkey=3hx029pogg6h6pwfs7h9d6l8j&amp;e=3&amp;dl=0">Updated Firmware files (Dropbox)</a></li>
<li><a href="https://github.com/weedo3d/MiniDeltaV2firmware">Printer Firmware Github (Note may not work)</a></li>
</ul>
<h2 id="instructionslinks">Instructions/Links</h2>
<p>Below are a series of instructions that may prove useful as well as a couple of links that may provide more information if issues arise.</p>
<h3 id="ch340ch341ch34x-driver-instructions">CH340/CH341/CH34X Driver Instructions</h3>
<h4 id="reason-to-use-these-instructions">Reason To Use These Instructions</h4>
<p>The printer is plugged in. But does not appear under /dev/ttyUSB0 or /dev/ttyACM0. <strong>May</strong> also help with general communication issues.</p>
<h4 id="links">Links</h4>
<ul>
<li>Following Instructions Derived From these for the Rasberry Pi and the X40 Printer
<ul>
<li><a href="https://www.weedo3dprinter.com/doku.php/x40/updatech340">https://www.weedo3dprinter.com/doku.php/x40/updatech340</a></li>
<li>Originally linked <a href="https://www.reddit.com/r/mpminidelta/comments/peotfz/problems_with_mini_delta_v2_and_octoprint/">in this Reddit post</a>.
<ul>
<li><strong>WARNING:</strong> Links leading to <code class="language-plaintext highlighter-rouge">mpminideltav2.com</code> no longer lead to a wiki. And will lead you no where safe as of writing this. If you want to attempt to use those links attempt to view them through the Way Back Machine. <a href="https://web.archive.org/web/20221205011655/http://mpminideltav2.com/doku.php?id=functional_video_display:mp_mini_delta_v2">Here is a link to the wiki on the Way Back Machine</a></li>
<li>This <a href="https://community.octoprint.org/t/monoprice-mini-delta-v2/36858">community post</a> on Octoprint’s site attempts to lead you to a similar tutorial with the same issue as above.</li>
</ul>
</li>
</ul>
</li>
<li>Source Code Used
<ul>
<li>The Source Code Provided in the Original Instructions did not compile without errors. This seems to compile without those errors.</li>
<li><a href="https://github.com/juliagoda/CH341SER">https://github.com/juliagoda/CH341SER</a></li>
</ul>
</li>
</ul>
<h4 id="instructions">Instructions</h4>
<p><strong>MAKE A BACKUP BEFORE PROCEEDING</strong> These instructions have also only been tested on Ubuntu and Debian. You have been warned.</p>
<ol>
<li>Delete the original Driver
<ul>
<li>This should be found at <code class="language-plaintext highlighter-rouge">/lib/modules/(version)/kernal/drivers/usb/serial/ch341.ko</code>. For me, it was <code class="language-plaintext highlighter-rouge">/lib/modules/5.4.0-189-generic/kernal/drivers/usb/serial/ch341.ko</code>.</li>
</ul>
</li>
<li>Install <code class="language-plaintext highlighter-rouge">build-essentials</code>
<ul>
<li>Using the command <code class="language-plaintext highlighter-rouge">sudo apt install build-essential</code> install the tools needed to compile the driver.</li>
</ul>
</li>
<li>Install <code class="language-plaintext highlighter-rouge">git</code>
<ul>
<li><code class="language-plaintext highlighter-rouge">sudo apt install git</code></li>
</ul>
</li>
<li>Install Linux headers files
<ul>
<li>This can be done with <code class="language-plaintext highlighter-rouge">sudo apt install linux-headers-$(uname -r)</code>.</li>
<li>If you prefer to not use <code class="language-plaintext highlighter-rouge">uname</code> to get the correct name. You can replace <code class="language-plaintext highlighter-rouge">$(uname -r)</code> with the value of <code class="language-plaintext highlighter-rouge">(version)</code> found in the first step.</li>
</ul>
</li>
<li>Clone the GitHub Repository
<ul>
<li><code class="language-plaintext highlighter-rouge">git clone https://github.com/juliagoda/CH341SER.git</code></li>
</ul>
</li>
<li>Compile the new Driver by navigating into <code class="language-plaintext highlighter-rouge">CH341SER</code> and then running <code class="language-plaintext highlighter-rouge">make</code>.
<ul>
<li><code class="language-plaintext highlighter-rouge">cd CH341SER; make</code></li>
</ul>
</li>
<li>Move the driver into position and install it.</li>
<li><code class="language-plaintext highlighter-rouge">make</code> creates a file called ch34x.ko. It needs to be moved into the same place the old driver was removed from. So it should be something like <code class="language-plaintext highlighter-rouge">/lib/modules/(version)/kernal/drivers/usb/serial/</code>. For me, it was <code class="language-plaintext highlighter-rouge">/lib/modules/5.4.0-189-generic/kernal/drivers/usb/serial/</code>.
<ul>
<li>This can be done using move or by making a copy.</li>
<li><code class="language-plaintext highlighter-rouge">sudo cp ch34x.ko /lib/modules/(version)/kernal/drivers/usb/serial/</code></li>
</ul>
</li>
<li>Run <code class="language-plaintext highlighter-rouge">sudo depmod</code> to initialize the drivers.</li>
<li>To check that the driver is installed correctly you can plug in the printer using a USB cable and check for <code class="language-plaintext highlighter-rouge">/dev/ttyUSB0</code>’s existence.</li>
</ol>
<h3 id="modify-octoprint-to-function-with-monoprice-mini-delta-v2">Modify Octoprint to function with Monoprice Mini Delta V2</h3>
<h4 id="reason-to-use-these-instructions-1">Reason To Use These Instructions</h4>
<p>WEEDO 3D printers and 3D printers that make use of WEEDO 3D printer boards need this modification in order to allow Octoprint to connect. They do provide their own “version” of Octoprint but I will not advise you to trust it. Especially with how simple the modification is.</p>
<h4 id="links-1">Links</h4>
<ul>
<li>Original Instructions for WEEDO X40 3D Printer
- <a href="https://www.weedo3dprinter.com/doku.php/tina2/octoprint">https://www.weedo3dprinter.com/doku.php/tina2/octoprint</a></li>
</ul>
<h4 id="reference-images">Reference Images</h4>
<h5 id="before-octoprint-modification">Before Octoprint Modification</h5>
<div class="image-box">
<div id="image7">
<img title="" src="/assets/images/first-3D-printer/Before_Octoprint_Mod.png" alt="" /><p></p>
</div>
</div>
<h5 id="after-octoprint-modification">After Octoprint Modification</h5>
<div class="image-box">
<div id="image8">
<img title="" src="/assets/images/first-3D-printer/After_Octoprint_Mod.png" alt="" /><p></p>
</div>
</div>
<h4 id="code-reference">Code Reference</h4>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#set default
</span><span class="n">serial_obj</span><span class="p">.</span><span class="n">setDTR</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="n">setRTS</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="nb">open</span><span class="p">()</span> <span class="c1"># Line 3907 of the original source.
# force reboot printer
</span><span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="n">setDTR</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="n">setRTS</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="n">setDTR</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="n">serial_obj</span><span class="p">.</span><span class="n">setRTS</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="c1"># wait
</span><span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="instructions-1">Instructions</h4>
<p><strong>MAKE A BACKUP BEFORE PROCEEDING</strong> These instructions have also only been tested on Ubuntu and Debian. You have been warned.</p>
<p><strong>NOTE:</strong> These instructions are also assuming you have already installed Octoprint.</p>
<ol>
<li>Find where <code class="language-plaintext highlighter-rouge">comm.py</code> is for your Octoprint install.
<ul>
<li>For me using <a href="https://github.com/paukstelis/octoprint_deploy">octoprint_deploy</a> to install Octoprint <code class="language-plaintext highlighter-rouge">comm.py</code> can be found at <code class="language-plaintext highlighter-rouge">~/OctoPrint/lib/python3.8/site-packages/octoprint/util/comm.py</code>.</li>
<li>The <code class="language-plaintext highlighter-rouge">python3.8</code> in the path is based on the current version of Python that Octoprint is installed with. It may be different in the future.</li>
</ul>
</li>
<li>Modify <code class="language-plaintext highlighter-rouge">comm.py</code>
<ul>
<li>This modification takes place around <a href="https://github.com/OctoPrint/OctoPrint/blob/a8fff3930e3c3901bd560ca77656c281959134b3/src/octoprint/util/comm.py#L3907">line 3907</a>. As of August 25th this is when Octoprint opens the serial connection.</li>
</ul>
<ol>
<li>Using the <a href="#before-octoprint-modification">before octoprint modification</a> reference image find the current source code.</li>
<li>Make the modifications seen in <a href="#after-octoprint-modification">after octoprint modification</a>. The modified code and the reasons for it are referenced in <a href="#code-reference">the code reference</a>.</li>
</ol>
</li>
<li>Reboot your Octoprint instance in order to load the new source code.</li>
</ol></content><author><name></name></author><category term="weekly-posts" /><category term="3D-printing" /><summary type="html">It’s a whole lot later than I had originally planned to post about this. But here we are. My initial escapades with 3D printing since High School.</summary></entry><entry><title type="html">Razer Naga Trinity Follow-Up</title><link href="/electronics-repair/2024/11/11/mouse-repair-followup.html" rel="alternate" type="text/html" title="Razer Naga Trinity Follow-Up" /><published>2024-11-11T00:00:00-06:00</published><updated>2024-11-11T00:00:00-06:00</updated><id>/electronics-repair/2024/11/11/mouse-repair-followup</id><content type="html" xml:base="/electronics-repair/2024/11/11/mouse-repair-followup.html"><p>Well, here I am again. I had originally planned on posting about my 3D printers as my next post. But that has changed.</p>
<p>Now you get a short post following up on the repair I posted about <a href="/electronics-repair/2022/04/17/mouse-repair.html">April 17th of 2022</a> where I repaired my Naga Trinity’s left click.</p>
<h2 id="the-follow-up">The Follow Up</h2>
<p>Well. After around 2 years the switch once again began failing in the same darn way. Where it would double-click. Likely due to corrosion within the switch from slight arcing. Or at least that’s my best guess. And beyond my best judgment, because I had completely forgotten how difficult it was to remove the switch. I decided I needed to repair it again. With the same iron that was a poor choice last time because the battery on my new one wasn’t charged.</p>
<p>The repair at this point does appear to be a success in terms of restoring functionality once again. The whole job took about 75 minutes this time compared to the 2 hours of soldering I fought through last time. This time it was 10 minutes of me trying to find the screws to realize I had two sets of skids on the mouse. And another hour of struggling to desolder the switch again. Once again destructively removing it.</p>
<h2 id="the-results">The Results</h2>
<p>But hey. She’s once again fixed. This time with no extra materials needing to be purchased. I just used what I had from last time. But this experience CONCRETELY confirms the fact I need a better iron for this kind of thing. I also need to make sure to put the kittens away next time so they don’t decide that the screws on my work mat are toys. Because I am now down a screw. But I’m “fine” with that since it’s my mouse. Not like I plan on selling it later.</p>
<p>It also appears that since the last time I did this Razer appears to have replaced the Naga Trinity with the Naga Pro V2. So I might get myself one of those if this sucker crokes again. First I’m going to wait for the 2-year mark to hit for its release to see if it suffers the same problem.</p>
<p>Either way. HOPEFULLY the next post is about my printers. That’s the plan atleast.</p></content><author><name></name></author><category term="electronics-repair" /><summary type="html">Well, here I am again. I had originally planned on posting about my 3D printers as my next post. But that has changed.</summary></entry><entry><title type="html">Gotify Repeater Plugin Upgrades (V2 Release)</title><link href="/weekly-posts/golang/gotify-repeater-plugin/2024/06/15/Gotify-Relay-Plugin-Update.html" rel="alternate" type="text/html" title="Gotify Repeater Plugin Upgrades (V2 Release)" /><published>2024-06-15T00:00:00-05:00</published><updated>2024-06-15T00:00:00-05:00</updated><id>/weekly-posts/golang/gotify-repeater-plugin/2024/06/15/Gotify-Relay-Plugin-Update</id><content type="html" xml:base="/weekly-posts/golang/gotify-repeater-plugin/2024/06/15/Gotify-Relay-Plugin-Update.html"><p>I am pleased to announce the release of 2024.2.36 of the plugin. This update was mostly about bug fixes. But does include some new features.</p>
<h2 id="general-improvements">General Improvements</h2>
<p>Overall most of the changes were to make it easier to develop this plugin further in the future. Such as by automating building and releasing versions on Github when something new is pushed to the <code class="language-plaintext highlighter-rouge">master</code> branch. This also included refactoring some code to reduce some confusion I was having with my organization. Among other things.</p>
<h3 id="upgrades-to-release-pipeline">Upgrades to Release Pipeline</h3>
<p>For the automation of the release pipeline, I ended up automating a couple of other tasks as well. Such as the automatic generation and setting of version numbers within builds. Which is currently achieved using <code class="language-plaintext highlighter-rouge">ldflags</code> at build time.</p>
<p>Along with that I also gave myself the ability to have two different release types. One that is triggered manually (prereleases) and another that is triggered automatically (full releases). The logic to this being that I want all full releases to be automatically built when I must to <code class="language-plaintext highlighter-rouge">master</code>. Whereas with pre-releases that I might release in the development of a full release, I wouldn’t want to be constantly generated. So they are triggered manually.</p>
<p>I also ended up building a bash script that looks at the current git repository and spits out a version number. Allowing me to “never” think about how I’m numbering things again.</p>
<h3 id="change-logs-can-be-nice">Change Logs Can Be Nice</h3>
<p>As part of the automation of releases, I also ended up setting up a method of writing and maintaining a change log. Currently, there are three files <code class="language-plaintext highlighter-rouge">CHANGELOG.md</code>, <code class="language-plaintext highlighter-rouge">MAJOR_CHANGELOG.md</code>, and <code class="language-plaintext highlighter-rouge">MINOR_CHANGELOG.md</code>. With <code class="language-plaintext highlighter-rouge">CHANGELOG.md</code> being all changes throughout the history of the project. The other two files are then being used in the release process to populate the release message body. <code class="language-plaintext highlighter-rouge">MAJOR_CHANGELOG.md</code> for the full releases and <code class="language-plaintext highlighter-rouge">MINOR_CHANGELOG.md</code> for the pre-releases. Simple enough.</p>
<p>Adding these files (and their semi-static location) also allowed for me to add a Change Log message within the plugin’s info page within Gotify. With it currently listing only the most recent changes.</p>
<h3 id="refactoring">Refactoring</h3>
<p>As for refactoring. I did not name all my packages in the most sane way when I started. And with my plan to expand the project further. Well, that wouldn’t be wise to leave as is. So before it gets too big I decided to try to come up with some more sane and descriptive package names.</p>
<p>We’ll see how well I can maintain it.</p>
<h3 id="security-through-obscurity">Security Through Obscurity</h3>
<p>As for functionality. The only thing that I wouldn’t call a full-fledged feature is the fact that Discord Webhook Links (and in the future other secrets) are now hidden by default unless you hover/click them.</p>
<p>And it is now that I would like to note. Much of the functionality of this plugin is based on my use. And that includes the fact that keys are currently transmitted back to the browser for the transmitters. In the future, I may make it so that isn’t the case. But for now. I do not have an explicit reason to develop said feature due to my current setup.</p>
<h2 id="new-features">New Features</h2>
<p>Most of these features are simple. And for good reason. The goal was to do this in a month. (Mostly because my projects need a limited timeline otherwise they tend to expand.) So they are as follows.</p>
<ul>
<li>Transmitters now record how many times they have been used.</li>
<li>Log messages for the plugin are now accessible in the UI.
- Log messages in UI are cleared on reboot but are still sent to Standard Out as well.</li>
<li>New Relay Transmitters
- Discord Advance
- Makes use of Discord’s Embeds that can be in Webhook messages.
- Embeds are not currently fully utilized at this point.
- The current implementation is more or less the same as the old Discord Transmitter.
- Pushbullet
- Very similar formatting to the original Discord Transmitter. Just through Pushbullet.</li>
</ul>
<h2 id="bug-fixes-every-project-needs-its-exterminator">Bug Fixes (Every project needs it’s exterminator)</h2>
<p>And as with many projects. A few bugs are likely to find their way in. And I am not impervious to bugs. So I had a few to fix. Unfortunately, I didn’t discover them until running the plugin myself for a couple of days. They are listed below.</p>
<ul>
<li>Fixed the issues that caused the Transmitters to be spammed with empty messages.
- Typically happened when the server Gotify was running on had a hiccup elsewhere.
- Prevention measures put in place to prevent them in the future.</li>
<li>Corrected method of reconnecting after a connection is unexpectedly terminated.
- This could also cause the issue above if it were to happen in a specific case.
- Currently, if a connection is lost. It will now wait 1 second before trying to reestablish itself.</li>
</ul>
<h2 id="and-thats-that">And That’s That</h2>
<p>And in all honesty that’s it. Most of this was a rehashing of what you can find in the project’s changes logs <a href="https://github.com/CEKlopfenstein/gotify-repeater/blob/master/CHANGELOG.md">here</a>. But I’ve set myself a goal of a post a month. So here it is. At least it has some value.</p>
<p>Have a good one. And see y’all next month where I hope to talk about 3D printing. Hopefully, everything pans out.</p></content><author><name></name></author><category term="weekly-posts" /><category term="golang" /><category term="gotify-repeater-plugin" /><summary type="html">I am pleased to announce the release of 2024.2.36 of the plugin. This update was mostly about bug fixes. But does include some new features.</summary></entry><entry><title type="html">Halite 1 - Site Resurrection Attempt</title><link href="/weekly-posts/halite-1/2024/05/15/Halite-1.html" rel="alternate" type="text/html" title="Halite 1 - Site Resurrection Attempt" /><published>2024-05-15T00:00:00-05:00</published><updated>2024-05-15T00:00:00-05:00</updated><id>/weekly-posts/halite-1/2024/05/15/Halite-1</id><content type="html" xml:base="/weekly-posts/halite-1/2024/05/15/Halite-1.html"><p>I first got seriously into programming sometime around my freshman year of High School. This is for a few reasons. But one of the reasons I remember doing so was because of a Minecraft Mod called Computercraft. The mod itself added computers as well as robots, called turtles, that players could program in <a href="https://www.lua.org/">Lua</a>. And I did this quite a bit and it allowed me to get over some self-doubts. As a result of not wanting to test my programs in the limited environment of the game. I later installed a Lua Interpreter. Which allowed for the creation of quite a few other projects I did. And while most are effectively lost at this point. It was a start to my journey.</p>
<p>Quite a few years later. In my senior year of High School, I ended up finding another “game” that allowed me to discover a fascination with simulations. As well as the idea of programming within a contest. That “game” was <a href="https://en.wikipedia.org/wiki/Halite_AI_Programming_Competition">Halite</a>. And while I never ended up participating in an active contest I enjoyed making bots that could play against each other. Though I can’t say I was ever really good. Either way, it was added to an early version of my Projects list. Though without much description of what to do around it.</p>
<h2 id="the-present">The Present</h2>
<p>Now here we are. It’s nearly 5 years later and I’m finally getting around to it. Unfortunately many of the original resources for Halite 1 at this point have mostly been pulled offline beyond the <a href="https://github.com/HaliteChallenge/Halite">Open Source Repositories on Github</a>. With the old domains such as <code class="language-plaintext highlighter-rouge">halite.io</code> and <code class="language-plaintext highlighter-rouge">2016.halite.io</code> are now redirected to the company that created the contest. Who seems to have decided to take all the Halite sites down sometime between September 1<sup>st</sup> 2022 and October 6<sup>th</sup> 2022 according to the <a href="https://web.archive.org/web/20220101000000*/https://halite.io/">Internet Archive</a>. With all requests to <code class="language-plaintext highlighter-rouge">halite.io</code> being redirected after October 2022. However, it also indicates that the last successful capture was a 3xx redirect. Aligning with the closing of their last contest.</p>
<h2 id="the-plan">The Plan</h2>
<p>So with the last remaining official resource outside of the Internet Archive being the Github repository. I decided that I would attempt to resurrect the site. At least in a static capacity. I might try to do more in the future. But for now. That’s my set goal.</p>
<h2 id="containerize-the-site">Containerize The Site</h2>
<p>The first up was to get a “working” containerization of the Website running. Though the instructions within the repository are for a full install of Ubuntu, I’m going this route to reduce my system requirements. As flawed as that idea may be.</p>
<p>Looking at the <a href="https://github.com/CEKlopfenstein/Halite-1/blob/Website-Docker-Container/CONTRIBUTING.md">Contribution Guide</a> it should be as simple as running some install scripts. But the scripts for whatever reason didn’t work right off the back. So I broke the scripts into RUN commands for building the Docker Image. And this worked for the most part. At least from appearances. The problem though boiled down to the fact that the scripts were meant for a VM. Where installing something like Apache would result in a running service. So I had to figure out how to configure Apache and start it within the container.</p>
<p>So after some digging in the <a href="https://github.com/CEKlopfenstein/Halite-1/blob/master/admin/md/INSTALL.md"><code class="language-plaintext highlighter-rouge">admin</code></a> folder in the repository I found another set of install instructions. And while this didn’t solve all of my problems it did give me enough to <a href="https://github.com/CEKlopfenstein/Halite-1/blob/1df28ef85666b220b2878d16bedad5befc8f04b3/Dockerfile">get a working Dockerfile</a>. Though it’s not without it’s possible issues. But for now, it will work for what I’m doing.</p>
<h2 id="other-issues-and-considerations">Other Issues and Considerations</h2>
<p>Now that I had a working way of running the web server then came a couple of other things I need to consider.</p>
<p>One of which is in what format I wish to publish a static version of the site. For this project’s timeline, I don’t have the time to fully rewrite the site to be new. And that was never the intention. But what I don’t want is for my version of the site to be mistaken as the original. Or for an attempt to take credit.</p>
<p>Another issue is the number of “dead links” like the links to the forum. And while I have access to the “homepage’s” code I don’t have access to the forum’s code or data. And given it likely had quite a bit of user data I don’t think I’d want to handle that. There are quite a few dead links and even a few dead paths. Such as downloads to resources.</p>
<p>So with those issues in mind. My plan is as follows. I’m going to modify the Home page and the footer to label the site as a copy of the original. Likely with a link to an Internet Archive version of the site. As well as a link to the original repository. I will also be going through and removing links that lead to dead paths. And for links that lead to the forum I plan on replacing them with equivalent links to Internet Archive versions of it. They might not load as fast as the original forum. But they should at least provide connections to pages that are lost otherwise.</p>
<h2 id="deployment">Deployment</h2>
<p>As for deployment, I plan to use Github Pages as I have before. <a href="https://stackoverflow.com/questions/66610/how-can-i-create-a-site-in-php-and-have-it-generate-a-static-version">And using <code class="language-plaintext highlighter-rouge">wget</code></a> I created a static copy of the site. Allowing it to be easily hosted.</p>
<h2 id="future-plans">Future Plans</h2>
<p>And with this monthly project done. I will likely move on to the next one. However, I do have plans for this. Eventually, I want to better containerize the whole thing. As well as produce some documentation for it. Unfortunately, my current goal of a project a month does not give me the time necessary to do everything. But then again. If I didn’t have the time constraint I’d probably never get things done. So it’s a double-edged sword I suppose. Either way see you all next time. The results of this page are available <a href="https://ceklopfenstein.github.io/Halite-1/">here.</a></p></content><author><name></name></author><category term="weekly-posts" /><category term="halite-1" /><summary type="html">I first got seriously into programming sometime around my freshman year of High School. This is for a few reasons. But one of the reasons I remember doing so was because of a Minecraft Mod called Computercraft. The mod itself added computers as well as robots, called turtles, that players could program in Lua. And I did this quite a bit and it allowed me to get over some self-doubts. As a result of not wanting to test my programs in the limited environment of the game. I later installed a Lua Interpreter. Which allowed for the creation of quite a few other projects I did. And while most are effectively lost at this point. It was a start to my journey.</summary></entry><entry><title type="html">Setup And Installation Of Venus 1500 Notes</title><link href="/weekly-posts/2024/05/01/Setup-And-Installation-of-Venus-1500.html" rel="alternate" type="text/html" title="Setup And Installation Of Venus 1500 Notes" /><published>2024-05-01T00:00:00-05:00</published><updated>2024-05-09T00:00:00-05:00</updated><id>/weekly-posts/2024/05/01/Setup-And-Installation-of-Venus-1500</id><content type="html" xml:base="/weekly-posts/2024/05/01/Setup-And-Installation-of-Venus-1500.html"><blockquote>
<p>For those who missed the last post. The writing and publishing of the previous post was delayed. So this post intended for April 15<sup>th</sup> was also delayed. I plan on being back on schedule for the May 15<sup>th</sup> post. See ya then.</p>
</blockquote>
<p>Well, here we are again. Another month another post. Except this time it isn’t a project as much as it will be a repository of some information that I would like to share. So this isn’t going to be a single-flowing article.</p>
<p>A couple of months ago, I was asked if I could look at an electric sign and see if I could get it working. This was after they had already called the help desk and gotten nowhere. However, they were able to determine their USB to RS-232 adapter was working. So I was asked if I could assist and do some digging. And those efforts bore fruit this time. So with that in mind, this post exists as a cleaner form of the notes I took while doing that.</p>
<h2 id="links-to-resources-used">Links To Resources Used</h2>
<ul>
<li><a href="https://www.daktronics.com/en-us/support/venus-1500-learning-center/download">Venus 1500 V4 Download</a></li>
<li><a href="https://www.microsoft.com/en-us/download/details.aspx?id=57473">Microsoft SQL Server 2014 Service Pack 3 Express Download</a></li>
<li><a href="https://www.daktronics.com/en-us/support/kb/DD3302758">Manual Installation of Microsoft SQL 2014 for Venus 1500 Software</a></li>
<li><a href="https://www.daktronics.com/web-documents/Manuals/ED-13744.pdf">Galaxy Outdoor Series AF-3190 Manual</a></li>
<li><a href="https://www.daktronics.com/web-documents/manuals/ed-13932.pdf">Venus 1500 Radio Manual - Gen 2</a></li>
</ul>
<h2 id="table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#determine-sign-make-and-model">Determine Sign Model And Information</a></li>
<li><a href="#installation-of-sql-express">SQL Express 2014 Installation</a></li>
<li><a href="#installation-of-venus-1500">Venus 1500 Installation Notes</a></li>
<li><a href="#connecting-with-the-sign">Connecting Venus 1500 to the Sign</a></li>
</ul>
<h3 id="determine-sign-make-and-model">Determine Sign Make and Model</h3>
<p>The information below was derived from the <a href="https://www.daktronics.com/web-documents/Manuals/ED-13744.pdf">Galaxy Outdoor Series AF-3190 Manual</a>.</p>
<ol>
<li>Turn off the sign.</li>
<li>Wait at least 30 seconds (allow the sign to fully turn off) and turn it back on.</li>
<li>Record the next 16 lines of text that the sign provides you. They provide the following information in order. Example values are in parentheses.
<ol>
<li>Product Name (Galaxy®)</li>
<li>Display Size (Row x Column)</li>
<li>Shading (64 Mono)</li>
<li>Bootloader Version (OS X.XX)</li>
<li>Firmware Number (ED13305)</li>
<li>Firmware Revision (Rev X.XX)</li>
<li>Hardware Address (HW:XX)</li>
<li>Software Address (SW:XX)</li>
<li>IP Address: ((default) 172.16.192.25)</li>
<li>Subnet Msk: ((default) Msk: 255.255.0.0)</li>
<li>COM 1 Configuration (C1:V15) ((Modem C1:V15) If a Modem is present)</li>
<li>COM 2 Configuration (C2:RTD)</li>
<li>Socket 3001: (IP 3001: V15)</li>
<li>Socket 3002: (IP 3002: RTD)</li>
<li>Line Frequency (CLK: AUTO 60 Hz)</li>
<li>Display Name Description (Galaxy Row x Column)</li>
</ol>
</li>
<li>Once the sign has listed the last item it will proceed with displaying the last stored settings. If it is the first time the sign has booted a pixel in the lower right-hand corner will flash to indicate that it is on.</li>
</ol>
<p>I would recommend writing down this information somewhere you can easily find it later. As it is useful when configuring the sign once you have Venus 1500 installed.</p>
<h3 id="installation-of-sql-express">Installation of SQL Express</h3>
<p>During testing, it was found that for the most reliable success in the installation of Venus 1500, it is best to manually install SQL Express first. Before attempting to install Venus 1500. Even though the installer for Venus 1500 is supposed to install the SQL Server itself. It appears to fail relatively frequently. (Or at least in my experience of around 20 attempts total.) Official instructions are available <a href="https://www.daktronics.com/en-us/support/kb/DD3302758">here</a> or using the <a href="#links-to-resources-used">Links to Resources Used</a> section. Follow them as they are written. I did not find any needed changes. Download link for <a href="https://www.microsoft.com/en-us/download/details.aspx?id=57473">SQL Express 2014</a> is available at the link or in the <a href="#links-to-resources-used">Links to Resources Used</a> section.</p>
<h3 id="installation-of-venus-1500">Installation of Venus 1500</h3>
<p>For installing Venus 1500 V4 following the instructions posted on their site is advised and can be <a href="https://www.daktronics.com/en-us/support/venus-1500-learning-center/download">found here</a> along with the download for the software. However, I would advise installing <a href="#installation-of-sql-express">the SQL server</a> manually due to some issues with the installer failing to do it on its own.</p>
<p>Beyond that, I have one other piece of advice while going through the install wizard.</p>
<div style="width:100%">
<img src="/assets/images/venus-1500/RestartDialog.jpg" style="margin:auto;display:block;" />
</div>
<p>Occasionally, in a dialog I was unable to capture, the install process may ask you to attempt to either restart a service or reboot the PC later. If this option is presented to you I recommend performing a reboot. This is for a couple of reasons. For example. If it fails to restart the service, at least from my testing, it reverts your install process. Whereas if you select the option to restart later it can complete its installation. I would also suggest you manually restart rather than clicking <strong>yes</strong> in the above dialog.</p>
<h3 id="connecting-with-the-sign">Connecting with the Sign</h3>
<p>Using the wizard will work just fine for most applications. The one thing I’d like to note is you will need to ensure that the USB to RS232 adapter is assigned to COM1 or COM2. This was the main issue I ran into when assisting with the sign. We could get the program to boot. Everything else worked. But we could not communicate with the sign through RS232. With the adapter not even indicating activity using it’s TX and RX lights.</p>
<p>What appears to be the issue is for some reason Windows 10 does not like to properly use Serial devices with the correct protocal for RS232 on anything other than COM1 or COM2. I actually remembered reading this somewhere. But unfortunately I can’t seem the original source. And attempting to Google it leads to people having problems with the Adapter itself that may or may not be related.</p>
<h4 id="how-to-change-portcheck-the-port">How to Change Port/Check the Port</h4>
<ol>
<li>
Open the Windows Device Manager and find the Ports list.
<ul>
<li>You can get to Windows device Manager by right clicking the Start Menu.</li>
</ul>
<div style="width:100%">
<img src="/assets/images/venus-1500/step1.jpg" style="margin:auto;display:block;" />
</div>
</li>
<li>
Click "Show hidden devices"
<ul>
<li>This may not be needed if you already see your device listed.</li>
</ul>
<div style="width:100%">
<img src="/assets/images/venus-1500/step2.jpg" style="margin:auto;display:block;" />
</div>
<div style="width:100%">
<img src="/assets/images/venus-1500/step3.jpg" style="margin:auto;display:block;" />
</div>
</li>
<li>
Enter the properties of your device.
<div style="width:100%">
<img src="/assets/images/venus-1500/step4.jpg" style="margin:auto;display:block;width:100%;max-width:400px" />
</div>
</li>
<li>
Go the "Port Settings" Tab.
<div style="width:100%">
<img src="/assets/images/venus-1500/step5.jpg" style="margin:auto;display:block;" />
</div>
</li>
<li>
Click "Advanced"
<div style="width:100%">
<img src="/assets/images/venus-1500/step6.jpg" style="margin:auto;display:block;" />
</div>
</li>
<li>
Click the "COM Port Number:" dropdown to change the port.
<ul>
<li>If your serial adapter is already on COM1 or COM2 then you can stop here.</li>
<li>If your serial adapter is not on COM1 or COM2 then change it and click OK.</li>
<li>If COM1 or COM2 have a "(in use)" beside them you will need to determine which device is on it and change it first. Using the steps above.</li>
</ul>
<div style="width:100%">
<img src="/assets/images/venus-1500/step7.jpg" style="margin:auto;display:block;" />
</div>
</li>
</ol></content><author><name></name></author><category term="weekly-posts" /><summary type="html">For those who missed the last post. The writing and publishing of the previous post was delayed. So this post intended for April 15th was also delayed. I plan on being back on schedule for the May 15th post. See ya then.</summary></entry><entry><title type="html">Gotify Repeater Plugin</title><link href="/weekly-posts/golang/gotify-repeater-plugin/2024/04/13/GotifyRepeaterPlugin.html" rel="alternate" type="text/html" title="Gotify Repeater Plugin" /><published>2024-04-13T00:00:00-05:00</published><updated>2024-04-13T00:00:00-05:00</updated><id>/weekly-posts/golang/gotify-repeater-plugin/2024/04/13/GotifyRepeaterPlugin</id><content type="html" xml:base="/weekly-posts/golang/gotify-repeater-plugin/2024/04/13/GotifyRepeaterPlugin.html"><blockquote>
<p>I would like to note. This was originally planned for completion and release on the 15th of March. That time has come and gone. And as a result, I would like to say that the April post may also be delayed. The normal schedule will hopefully be restored by May.</p>
</blockquote>
<p>Since sometime in November I’ve been running a <a href="https://gotify.net/">Gotify Server</a> on my Proxmox Instance. With the initial intention being to use it to receive notifications from Proxmox. And for that, Gotify functions fine. The issue though is that it’s missing on feature I wanted. And that being the ability for the server to act as a repeater/proxy for other services that can send out notifications. But since Gotify has plugin support I figured I could write up that functionality myself. So I got started on figuring out how.</p>
<h2 id="project-setup">Project Setup</h2>
<p>Now luckily enough finding the documentation for it was pretty easy. They have a GitHub Repository called <a href="https://github.com/gotify/plugin-template">plugin-template</a> (what a creative name) that provides the needed files for a simple “hello world” plugin.</p>
<p>Unfortunately, it didn’t work quite write on the Ubuntu VM have been SSHing into to tinker with Go. The first of being that I have not configured Docker to be able to be run without using <code class="language-plaintext highlighter-rouge">sudo</code>. But that was a simple fix by just adding it to the Makefile.</p>
<p>The other issue. Which seemed strange. Was the inability to find a program called <code class="language-plaintext highlighter-rouge">gomod-cap</code>. With the commands originally being <code class="language-plaintext highlighter-rouge">gomod-cap -from ${BUILDDIR}/gotify-server.mod -to go.mod</code>. Looking through the Makefile I found that one of the Makefile’s targets appears to “install” it using <code class="language-plaintext highlighter-rouge">GO111MODULE=off go get -u github.com/gotify/plugin-api/cmd/gomod-cap</code>. Unfortunately, while that was functioning. It didn’t install it in such a way that it would be runnable as a stand-alone command.</p>
<p>So instead I replaced <code class="language-plaintext highlighter-rouge">gomod-cap</code> within that command to <code class="language-plaintext highlighter-rouge">go run github.com/gotify/plugin-api/cmd/gomod-cap</code> which works like <code class="language-plaintext highlighter-rouge">npx</code> in the <code class="language-plaintext highlighter-rouge">npm</code> world. Where it downloads the script as a temp file and executes it. Now that’s likely not the best solution but that’s what I did to get started. Eventually, I’ll get around to understanding why it didn’t work. But on with the project.</p>
<p>From there I modified it to Makefile to give me a target that would build the amd64 version of the plugin and then place it in the correct location where a <code class="language-plaintext highlighter-rouge">docker-compose</code> file would pick it up for a test gotify server. Loading the newest version of the plugin into a gotify server every time I ran the target. Which finally gave me an environment to at least test whether this idea would even work.</p>
<h2 id="initial-prototype">Initial Prototype</h2>
<p>So first up I created a VERY simple demo/prototype just to make sure what I was going to do was even achievable. Initially, I had thought that there would be the ability to hook into an event listener. But that doesn’t exist directly within the Plugin interface. So what I ended up doing, and what someone else did in a <a href="https://github.com/elgonlabs/gotify-emailer/">different plugin that does this to send emails</a> after the fact, was to use the REST API to get a WebSocket connection that does exactly what I need.</p>
<p>That requires two things then. An HTTP or HTTPS path to the gotify server, cause we can’t guarantee it will be within docker or on a different port than standard. And a client token that can be used for authentication. So this being a proof of concept I simply hard-coded those values.</p>
<p>From there I ended up fiddling around and failing quite frequently to get a Websocket to connect when the server started up and the plugin enabled itself. Until I realized that I was attempting to connect to nothing because Gotify initializes the plugins before it starts accepting traffic. 🤦</p>
<p>But after that was fixed. I then created a loop that would read the messages received from the socket and send out a Discord Webhook every time it received something. And what would you know? It worked. After some trial and error and learning more about how Go handles JSON. But hey can’t make an omelet without breaking a few legs. But it worked as a proof of concept.</p>
<h2 id="version-0">Version 0</h2>
<p>But then I wanted to make something with what I considered the bare minimum of functional, at least for a kind of Version 0. So I came up with a list of requirements.</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>Must Properly Handle enabling and disabling of the plugin.</li>
<li>No Hard Coded Values. (URL for Gotify, Client Token, Discord Webhook)</li>
<li>Configurable from within Gotity.</li>
<li>Simple Formatted Discord Message</li>
<li>Information about the plugin displayed within Gotify “Displayer” per the recommendations.</li>
</ul>
<p>All of which are relatively simple. Most of it more or less is refactoring the already existing demo and properly handling the errors that it might run into. But the result looked like this.</p>
<link rel="stylesheet" href="/assets/image-box.css" />
<div class="image-box">
<div id="image0" class="image-box-modal">
<img src="/assets/images/gotify-plugin/Plugin_Screen_1.jpg" alt="" />
</div>
<link rel="stylesheet" href="/assets/modal.css" /><div>
<div id="image0modal" class="modal">
<div class="modal-content">
<div>
<span id="image0modal-close" class="close">&times;</span>
</div>
<div>
<img src="/assets/images/gotify-plugin/Plugin_Screen_1.jpg" alt="" /></div>
</div>
</div>
<script>
// Open Modal Event Listener
document.getElementById("image0").onclick = function () {
document.getElementById("image0modal").style.display = "block";
// One of Two Modal Close Event Listeners (Click outside of Modal)
window.onclick = function (event) {
if (event.target == document.getElementById("image0modal")) {
document.getElementById("image0modal").style.display = "none";
}
}
}
// One of Two Modal Close Event Listeners (Click X to close)
document.getElementById("image0modal-close").onclick = function () {
document.getElementById("image0modal").style.display = "none";
}
</script>
</div>
</div>
<p></p>
<p>And overall it functions exactly as I was hoping it would though it’s not perfect.</p>
<h2 id="discovered-flawsnext-version">Discovered Flaws/Next Version</h2>
<p>Once I deployed version 0 of my plugin to my Gotify Server I decided to run some tests. Unfortunately, these tests presented some issues I didn’t have when running it for short periods when developing. Such as the fact that the plugin currently can not recover itself in the event the Websocket is closed for whatever reason. Requiring the user to disable and enable the plugin to fix it. Another issue and one I didn’t think I’d have to deal with is the fact that a Gotify event message can contain extra information. Which I discovered Proxmox makes use of. So that now needs to be handled and can’t be ignored. So onward with the improvements.</p>
<h2 id="version-1---working-so-much-better">Version 1 - Working So Much Better</h2>
<p>Now first off with version 1 came the corrections to quite a few bugs from testing. Which included the inability of the plugin to recover from connection issues. And while I’m not an expert at this point I think I’ve more or less solved it in this case. The problem with Proxmox was also solved relatively quickly by simply not parsing the entire message structure. Only parsing the parts I’m currently using which at this point seems to be the smarter move.</p>
<p>But fixing bugs wasn’t the only thing I wanted in version one. I also wanted to improve the interface for configuring the plugin. And while the Configurator is probably great for some projects. I wanted an interface that could guide the user. At least to some degree. So I began to implement it using the Webhooker Gotify provides and <a href="https://htmx.org/">HTMX</a>.</p>
<p>And I must say. I can see the usefulness of <a href="https://htmx.org/">HTMX</a>. It allowed me to not have to mess with front-end Javascript to be able to lazy load data or even submit data back to the server. You just have the HTTP server, or in this case, the Gotify server, to respond with properly formatted HTML, which provided me with a very simple way to easily build the kind of interface I needed. At least once I had the time and learned some of its quirks.</p>
<div class="image-box">
<div id="image1" class="image-box-modal">
<img src="/assets/images/gotify-plugin/Plugin_Interface.jpg" alt="" />
</div>
<div>
<div id="image1modal" class="modal">
<div class="modal-content">
<div>
<span id="image1modal-close" class="close">&times;</span>
</div>
<div>
<img src="/assets/images/gotify-plugin/Plugin_Interface.jpg" alt="" /></div>
</div>
</div>
<script>
// Open Modal Event Listener
document.getElementById("image1").onclick = function () {
document.getElementById("image1modal").style.display = "block";
// One of Two Modal Close Event Listeners (Click outside of Modal)
window.onclick = function (event) {
if (event.target == document.getElementById("image1modal")) {
document.getElementById("image1modal").style.display = "none";
}
}
}
// One of Two Modal Close Event Listeners (Click X to close)
document.getElementById("image1modal-close").onclick = function () {
document.getElementById("image1modal").style.display = "none";
}
</script>
</div>
</div>
<p></p>
<p>In creating the interface I also ended up adding some new features that can be seen in the image above. Such as the ability to more easily set the token that the plugin will use to listen for messages. As well as the ability to manage “transmitters” which is what I’m going to be calling the different destinations that the plugin can relay messages to. With there currently only being two. The “Log Transmitter” and one for Discord Webhooks. Eventually, I plan on expanding the selection but for now, this works for demonstration purposes and my current use.</p>
<p>All of this is being built using <a href="https://htmx.org/">HTMX</a> for the interaction with the server itself (all the APIs I created require a valid token) and Bootstrap 5 for some CSS. Which I’m pretty pleased about. And now with that done and working I can comfortably begin the next month’s project. Now to figure out what it is. Well, I have some ideas.</p>
<p>If anyone else may find this plugin useful it is currently available in a <a href="https://github.com/CEKlopfenstein/gotify-repeater">public Github Repo here</a>.</p></content><author><name></name></author><category term="weekly-posts" /><category term="golang" /><category term="gotify-repeater-plugin" /><summary type="html">I would like to note. This was originally planned for completion and release on the 15th of March. That time has come and gone. And as a result, I would like to say that the April post may also be delayed. The normal schedule will hopefully be restored by May.</summary></entry><entry><title type="html">Tinkering with Go and Google Sheets API (Budget Data Pulldown)</title><link href="/weekly-posts/golang/budget-app/2024/02/15/Learning-Go.html" rel="alternate" type="text/html" title="Tinkering with Go and Google Sheets API (Budget Data Pulldown)" /><published>2024-02-15T00:00:00-06:00</published><updated>2024-02-15T00:00:00-06:00</updated><id>/weekly-posts/golang/budget-app/2024/02/15/Learning-Go</id><content type="html" xml:base="/weekly-posts/golang/budget-app/2024/02/15/Learning-Go.html"><p>Well. It’s that time of month again. And this time I’ve been tinkering around with <a href="https://go.dev/">Go</a> by building a small program.</p>
<p>The program in question does a couple things. The intention being to pull data from a Google Sheet that I update with financial information and place that data into an SQLite database file. So first things first. Getting Google Sheets to work.</p>
<h2 id="google-sheets-authentication">Google Sheets Authentication</h2>
<p>The Google Sheets API, and other Google APIs those that interact with user data, require the use of OAuth2. Now I could have used an already developed Library for it. In fact Google has an official one for Go. Except I prefer to write my own. Especially for personal projects. And given the Sheet’s REST API isn’t to complicated I decided to give it a shot.</p>
<p>And it turns out that I’m by far the first one to want to do this. In fact someone wrote a rather <a href="https://rsseau.fr/2020-10-28-google-oauth">nice article</a> on how to Authenticate simply using <code class="language-plaintext highlighter-rouge">cURL</code>.</p>
<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// client id and secret obtained from Google API Console:</span>
<span class="c">// https://console.developers.google.com/apis/credentials</span>
<span class="k">var</span> <span class="n">clientId</span> <span class="kt">string</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"ID"</span><span class="p">)</span>
<span class="k">var</span> <span class="n">clientSecret</span> <span class="kt">string</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"SECERET"</span><span class="p">)</span>
<span class="c">// Redirect URL. Do not need any webserver for now because this will only</span>
<span class="c">// allow us to copy the redirection URL provided by Google</span>
<span class="k">var</span> <span class="n">redirect</span> <span class="kt">string</span> <span class="o">=</span> <span class="s">"http://localhost:8080"</span>
<span class="c">// Scope, that means actions you'll be able to make with obtained token</span>
<span class="c">// (this is a space separated list)</span>
<span class="k">var</span> <span class="n">scope</span> <span class="kt">string</span> <span class="o">=</span> <span class="s">"https://www.googleapis.com/auth/webmasters.readonly"</span>
<span class="k">var</span> <span class="n">link</span> <span class="kt">string</span> <span class="o">=</span> <span class="s">"https://accounts.google.com/o/oauth2/auth?client_id="</span> <span class="o">+</span> <span class="n">clientId</span>
<span class="o">+</span> <span class="s">"&amp;redirect_uri="</span> <span class="o">+</span> <span class="n">redirect</span> <span class="o">+</span> <span class="s">"&amp;scope="</span> <span class="o">+</span> <span class="n">scope</span>
<span class="o">+</span> <span class="s">"&amp;response_type=code&amp;access_type=offline"</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
<span class="c">// Get the value of CODE. Retrievable from resulting URL that the login link</span>
<span class="c">// redirects to.</span>
<span class="k">var</span> <span class="n">srv</span> <span class="n">http</span><span class="o">.</span><span class="n">Server</span>
<span class="k">var</span> <span class="n">code</span> <span class="kt">string</span>
<span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
<span class="n">codeResp</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">URL</span><span class="o">.</span><span class="n">Query</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"code"</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">codeResp</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
<span class="n">code</span> <span class="o">=</span> <span class="n">codeResp</span>
<span class="c">// Delayed shutdown of HTTP server</span>
<span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="n">time</span><span class="o">.</span><span class="n">Sleep</span><span class="p">(</span><span class="m">500</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Millisecond</span><span class="p">)</span>
<span class="n">srv</span><span class="o">.</span><span class="n">Shutdown</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">())</span>
<span class="p">}()</span>
<span class="p">}</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"You are now logged in."</span><span class="p">)</span>
<span class="p">})</span>
<span class="n">data</span> <span class="o">:=</span> <span class="n">url</span><span class="o">.</span><span class="n">Values</span><span class="p">{}</span>
<span class="c">// code from URL</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"code"</span><span class="p">,</span> <span class="n">code</span><span class="p">)</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"client_id"</span><span class="p">,</span> <span class="n">clientId</span><span class="p">)</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"client_secret"</span><span class="p">,</span> <span class="n">clientSecret</span><span class="p">)</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"redirect_uri"</span><span class="p">,</span> <span class="n">redirect</span><span class="p">)</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"access_type"</span><span class="p">,</span> <span class="s">"offline"</span><span class="p">)</span>
<span class="n">data</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"grant_type"</span><span class="p">,</span> <span class="s">"authorization_code"</span><span class="p">)</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Post</span><span class="p">(</span><span class="s">"https://accounts.google.com/o/oauth2/token"</span><span class="p">,</span>
<span class="s">"application/x-www-form-urlencoded"</span><span class="p">,</span> <span class="n">strings</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">Encode</span><span class="p">()))</span>
<span class="n">test</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
<span class="c">// Struct that can be contain the unmarshaled JSON</span>
<span class="k">var</span> <span class="n">responseJSON</span> <span class="n">responseBit</span>
<span class="c">/*
{
"access_token": "",
"expires_in": 3599,
"refresh_token": "",
"scope": "https://www.googleapis.com/auth/webmasters.readonly",
"token_type": "Bearer"
}
*/</span>
<span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">test</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">responseJSON</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Status</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"access token"</span><span class="p">,</span> <span class="n">responseJSON</span><span class="o">.</span><span class="n">Access_token</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"refresh token"</span><span class="p">,</span> <span class="n">responseJSON</span><span class="o">.</span><span class="n">Refresh_token</span><span class="p">)</span>
<span class="c">// Example of refreshing TOKEN using refresh Token</span>
<span class="n">refresh</span> <span class="o">:=</span> <span class="n">url</span><span class="o">.</span><span class="n">Values</span><span class="p">{}</span>
<span class="n">refresh</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"client_id"</span><span class="p">,</span> <span class="n">clientId</span><span class="p">)</span>
<span class="n">refresh</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"client_secret"</span><span class="p">,</span> <span class="n">clientSecret</span><span class="p">)</span>
<span class="n">refresh</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"refresh_token"</span><span class="p">,</span> <span class="n">responseJSON</span><span class="o">.</span><span class="n">Refresh_token</span><span class="p">)</span>
<span class="n">refresh</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"grant_type"</span><span class="p">,</span> <span class="s">"refresh_token"</span><span class="p">)</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Post</span><span class="p">(</span><span class="s">"https://accounts.google.com/o/oauth2/token"</span><span class="p">,</span>
<span class="s">"application/x-www-form-urlencoded"</span><span class="p">,</span> <span class="n">strings</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">refresh</span><span class="o">.</span><span class="n">Encode</span><span class="p">()))</span>
<span class="n">test</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
<span class="c">// Another struct that is very similar to responseBit minus the refresh token</span>
<span class="k">var</span> <span class="n">result</span> <span class="n">refreshInfo</span>
<span class="c">/*
{
"access_token": "",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/webmasters.readonly",
"token_type": "Bearer"
}
*/</span>
<span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">test</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">result</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Status: "</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">Status</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Access Token: "</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">Access_token</span><span class="p">)</span>
<span class="k">for</span> <span class="n">t</span> <span class="o">:=</span> <span class="m">0</span><span class="p">;</span> <span class="n">t</span> <span class="o">&lt;</span> <span class="m">10</span><span class="p">;</span> <span class="n">t</span><span class="o">++</span> <span class="p">{</span>
<span class="n">time</span><span class="o">.</span><span class="n">Sleep</span><span class="p">(</span><span class="m">10</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Above was effectively what I started out with. Using the instructions provided in the article I converted it to be useful in Go. And to test that whether the access token I used the following. With the token being placed within a HTTP Header.</p>
<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">req</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">MethodGet</span><span class="p">,</span>
<span class="s">"https://www.googleapis.com/oauth2/v3/tokeninfo"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
<span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="s">"Authorization"</span><span class="p">,</span> <span class="s">"Bearer "</span><span class="o">+</span><span class="n">access_token</span><span class="p">)</span>
<span class="n">client</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">body</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="kt">string</span><span class="p">(</span><span class="n">body</span><span class="p">))</span>
</code></pre></div></div>
<p>And with that the ability to get and use the access token I then needed a way to ensure that the access token maintains. And from doing some research I learned that this is possible with using a Go <code class="language-plaintext highlighter-rouge">channel</code> and a Ticker. Which was actually really simple. Then then led to mean learning how to make methods for Go <code class="language-plaintext highlighter-rouge">structs</code>. Do I then effectively packaged all of the Google authentication into a single package. With the access_token not being accessible outside of the package. Instead when you need to preform a request with the access token I have provided a method that is effectively a mapping of a basic <code class="language-plaintext highlighter-rouge">GET</code> request with the needed header added within it. The method then returning the body of the response or the error in the event something when wrong. The code below effectively being the same as in the code block directly above this paragraph except using the new Methods.</p>
<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">queryResult</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">auth</span><span class="o">.</span><span class="n">GetQuery</span><span class="p">(</span><span class="n">URL</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"PRINT INFO ERROR:"</span><span class="p">,</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"PRINT INFO: </span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="kt">string</span><span class="p">(</span><span class="n">queryResult</span><span class="p">))</span>
</code></pre></div></div>
<p>On top of this to prevent the need to constantly sign in. The program also saves the refresh token in a secure manner and attempt to use that first before requesting the user to log in.</p>
<h3 id="google-sheets-api">Google Sheets API</h3>
<p>Next came the access of the actual Google Sheet. Which first required me to enable the Google Sheet API for the OAuth 2.0 Client IDs I have provided the program. After that I then needed to add another scope. Which is achieved (as need above in a comment) by appending the correct Scope URL. Which is my case is <code class="language-plaintext highlighter-rouge">https://www.googleapis.com/auth/spreadsheets.readonly</code> which provides read only access to the Signed in User’s Google Sheets.</p>
<p>From there the REST API is quite <a href="https://developers.google.com/sheets/api/reference/rest">well documented</a> with all the requests I’m interested in being <code class="language-plaintext highlighter-rouge">GET</code> requests making it even simpler by not requiring formatted payloads. I just needed to create the needed <code class="language-plaintext highlighter-rouge">structs</code> that would allow me to interact with the data.</p>
<h2 id="sqlite-database">SQLite Database</h2>
<p>From there I had the ability to pull data. I had to determine how to store it in the SQLite database file. Now instead of implementing SQLite 3 myself I did use <a href="https://github.com/mattn/go-sqlite3">an external library</a> but given the complexity, and the fact I wanted to do this in a month, it was the best option.</p>
<p>Now most of the sheet’s within my Google Sheet have 6 Columns. Those being Date, Balance, Debit, Credit, Title, and Notes. With there being 9 different sheets that I wanted to pull down. So what I created two basic tables.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">account</span><span class="p">(</span>
<span class="n">account_id</span> <span class="nb">integer</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">account_name</span> <span class="nb">text</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">trans</span><span class="p">(</span>
<span class="n">trans_id</span> <span class="nb">integer</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">account</span> <span class="nb">integer</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nb">date</span> <span class="nb">TEXT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">amount</span> <span class="nb">integer</span><span class="p">,</span>
<span class="n">title</span> <span class="nb">text</span><span class="p">,</span>
<span class="n">notes</span> <span class="nb">text</span><span class="p">,</span>
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">account</span><span class="p">)</span>
<span class="k">REFERENCES</span> <span class="n">account</span> <span class="p">(</span><span class="n">account_id</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>
<p>One two store sheet names (and eventually other data about that particular sheet but hey that’s a future project) and another to store the actual transactions. Where instead of having three columns containing data on the transaction I have one. Being the change in the account. From there (and by making the assumption all accounts start at 0) I can then find the balance. And if I want to create a table similar to my old sheets I can use the following SQL command</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
<span class="n">trans_id</span><span class="p">,</span>
<span class="n">account</span><span class="p">,</span>
<span class="nb">date</span><span class="p">,</span>
<span class="n">printf</span><span class="p">(</span><span class="nv">"$%.2f"</span><span class="p">,</span><span class="n">ROUND</span><span class="p">((</span><span class="k">SELECT</span>
<span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span>
<span class="k">FROM</span> <span class="n">trans</span> <span class="k">AS</span> <span class="n">t</span>
<span class="k">WHERE</span> <span class="n">t</span><span class="p">.</span><span class="n">account</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="n">account</span>
<span class="k">AND</span> <span class="n">t</span><span class="p">.</span><span class="n">trans_id</span> <span class="o">&lt;=</span> <span class="n">w</span><span class="p">.</span><span class="n">trans_id</span>
<span class="p">),</span><span class="mi">2</span><span class="p">))</span> <span class="k">AS</span> <span class="n">balance</span><span class="p">,</span>
<span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">amount</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="k">THEN</span> <span class="n">printf</span><span class="p">(</span><span class="nv">"$%.2f"</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="o">*</span><span class="n">amount</span><span class="p">)</span> <span class="k">ELSE</span> <span class="s1">''</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">debit</span><span class="p">,</span>
<span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">amount</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">THEN</span> <span class="n">printf</span><span class="p">(</span><span class="nv">"$%.2f"</span><span class="p">,</span><span class="n">amount</span><span class="p">)</span> <span class="k">ELSE</span> <span class="s1">''</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">credit</span><span class="p">,</span>
<span class="n">title</span><span class="p">,</span>
<span class="n">notes</span>
<span class="k">FROM</span> <span class="n">trans</span> <span class="k">AS</span> <span class="n">w</span>
</code></pre></div></div>
<h3 id="data-loading">Data Loading</h3>
<p>From there came the need to load which was done with some simple <code class="language-plaintext highlighter-rouge">INSERT</code> statements and some logic that handled determine if the rows in the sheet where to be negative or positive. Which wasn’t to bad</p>
<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">values</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">sheet</span><span class="o">.</span><span class="n">GetValues</span><span class="p">(</span><span class="n">sheetName</span><span class="p">,</span> <span class="s">"A1:F"</span><span class="p">)</span>
<span class="n">transFound</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="n">values</span><span class="o">.</span><span class="n">Values</span><span class="p">)</span> <span class="o">-</span> <span class="m">1</span>