Month: April 2026

  • 硬體供應鏈危機

    隨著AI 的野蠻擴張,這個世界的硬體供應鏈似乎已然出現危機,因為AI 對於HDD 的需求過大,三大HDD 廠商的產能已經完全被訂購一空,8Tb HDD 的價格,居然比我幾年前買的時候,價格上漲了一倍。

    也就是說,如果現在我的NAS HDD壞掉一顆,我將要付出巨大的成本。

    這對於終端來說是危機,但對於三大HDD 廠商,何嘗不是機遇呢?所以有些人啊,非常狹隘,總是從自己的角度考慮問題。

    雲端服務商也開始漲價,但是,這對於核心客戶而言,不管漲價或是不漲價他們都是要用的,自建datacenter 又不是一朝一夕,對於散客而言,具備專業知識的,則會選擇中小型雲端服務商,不具備專業知識的,還是會選擇三大,文檔可能會更完備,必要的時候還可以找一下客服,但大多數問題其實可以問AI,根本不需要去找人。

    前幾天在路上看到一台好乾淨的檳榔車,無論是輪轂,還是腳踏板,車身,後照鏡,都很乾淨,可以趕得上日本了。

    肚臍眼左側還是會時不時的隱隱作痛,臺大醫院的肝膽腸胃科總是掛不到號,也不知道什麼什麼時候才能做得上腸鏡和胃鏡。

  • 例行的清明掃墓,度了一個劫

    倒不是因為觸了霉頭,我這個人不信邪,我說過,就算是超自然的力量奪走我的生命,我到死也不會承認他的存在。

    上山的前一天開始發燒,不知道是吃錯了什麼東西,還是收拾家裡陳舊的物品時,帶起來陳舊的細菌或是病毒,特別是我媽留下放了十年的那一堆酵素,每次回家我都要把他們倒掉然後扔掉瓶子,因為實在是太多了。也許是傾倒的時候吸入了某些細菌或病毒。


    發燒的第一天我自己並沒有任何感覺,但是Apple Watch開始告警,夜間的心率升高,靜息呼吸頻率也變高,我沒注意。

    看來這個東西是很準確的,Apple Watch 是個好夥伴。

    清明上山的時候除了肚子有點痛,頭有點痛,腳發軟,覺得睏,打電話的時候居然跟Miley 一樣不自覺的蹲在了地上,沒有其他的感覺,其實應該已經是燒到38度以上了。

    燒到第三天,叫小姑送我去醫院,然後到了社區診所,醫生摸了下脈搏,開了退燒藥,然後用生薑蓋在肚皮上,艾灸了10分鐘。

    當天晚上開始心率狂跳,一直出汗,折騰整夜。(應該是身體的白血球在劇烈的進攻細菌)

    總之呢,燒了四天之後,去了醫院急診,開始轉入感染科住院,血液檢查提示有感染,開始IV 廣譜抗生素,

    血檢報告提示:

    尿檢未見異常

    糞便檢測未見異常

    PCR 病毒檢測未見異常

    武漢肺炎 病毒檢測未見異常

    沒有嘔吐,沒有腹瀉,沒有咳嗽,除了肚臍左側有一點點痛和發燒,沒有其他任何症狀。

    IV 兩天針對好氧菌和厭氧菌的不同抗生素,體溫不但沒有下降,反而上升了一點點,從37.6 到了38,護士已經拿來了退燒藥,說告訴我讓我吃就吃。

    CT 做完,沒有看出什麼問題,醫生試圖以不明原因發燒忽悠過去,於是我要求做增強CT,然後增強CT 做出來,還是沒看出來什麼問題。

    我想,看得出來問題的話,我大概就出不了院了。

    要求強制出院,準備趕回台灣,最多是在機場入境被攔下來然後救護車送去醫院,總比在這小山村的醫院什麼都查不出來好。

    小姑和小姑丈一直在照料我,因為大概率可能是吃了他們家的東西出的問題,但根本原因還是我脆弱的腸胃。

    計劃是,妹妹幫我叫了一台車直接送我去重慶的酒店。

    Miley 當晚飛到重慶等我,第二天和我一起飛回去。

    雖然精神還是沒有很好,畢竟燒了六天,但是感覺問題不大,小又一定要來找我,於是在酒店見面。

    小又終於是又離婚了,怎麼說呢,小又一直沒什麼自我獨立意識,從小家裡富裕,不缺錢,但是因為媽媽的原因,又對婚姻和男人有著傳統意義上的要求,大概是因為她爸爸在男女關係上為所欲為,所以她反而沒有為所欲為,而是走了一條非常傳統的道路,真是讓人意外,本來以為她可以像她爸爸一樣,成為交際花的,在她開那間酒吧的時候,這是我對她的期待,算是落空了。

    小王也離婚了,葳君和我都不意外,畢竟她幾乎每次見面都會說她要離婚,但付諸實施後,她似乎並不希望更多人知道這個消息,看起來她只告訴了少數幾個人,好在兩個兒子應該有很多事情需要她忙,並不會讓她過於空閒而至於最後要去看身心科。

    勇君現在是ByteDance 重慶的市場總監,每天有很多巴結他的人,以前的同學也有很多巴結他,畢竟隨便放點流量過去,就可以把那隻豬吹上風口。

    至於從軍隊退役的野人,聽說每次都騙勇君說兩個人吃飯,然後等勇君去了就會發現一群地方領導等著給他敬酒……

    抵達桃園機場的時候,並沒有被溫度探測儀檢測到體溫過高,因為已經沒有發燒了,只好回家,第二天去了臺大醫院,剛好還能掛上一個號。

    開了抽血單,第三天從健保app 上看了結果,所有的指標都已經恢復正常。

    台灣的血檢數值和中國還不一樣,但對於不是醫生的我來說,不過也是看個熱鬧罷了。

  • 如何使用中央氣象署的api 獲取當前天氣和預報

    我之前一直通過n8n 使用OpenWeatherMap 的api 來獲取天氣和預報並通過pushover 發送到手機,但是,由於台灣是海島型氣候,天氣多變,預報往往不太準確,而比較權威的the weather channel 又不提供api,中央氣象署的api 其實我很早就註冊了,之前沒有AI 幫忙,他們的文檔又非常讓人困擾,所以沒有使用。

    今天讓AI 看了一下他們的文檔,終於替換掉了OpenWeatherMap 的api 。

    中央氣象署開放資料平臺之資料擷取API – https://opendata.cwa.gov.tw/dist/opendata-swagger.html#/

    除了實時天氣之外,還有預報信息,由於台灣很小,所以可以具體到某一個區域,例如板橋區。

    但問題在於,板橋區的實時天氣和預報是兩個不同的api,而UV index 則不是在所有的氣象監測站都有,比如板橋區就沒有,新北只有一個。

    所以,為了生成這麼一條信息,需要調取三個api。

    當然,首先您需要去註冊一個中央氣象署會員 – https://pweb.cwa.gov.tw/emember/register/authorization

    註冊成功後,在會員資料部分,從“API授權碼” 中申請一個授權碼,您將會在後面的每一個api call 中使用他。

    我記得我註冊的時候他還是中央氣象局,升級署了。

    天氣預報api,板橋區監測站

    https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-D0047-069?Authorization=YOUR-CWB-API-KEY&LocationName=板橋區

    實時天氣api,距離我最近的監測站

    https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=YOUR-CWB-API-KEY&StationId=C0AJ80

    UV index api, 只有比較大的站有。

    https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0003-001?Authorization=YOUR-CWB-API-KEY&StationId=466881

    之所以使用三個api 是因為實時天氣我選了距離我最近的一個,如果您不需要那麼準確,可以只用新北區的監測站就可以。

    n8n 的 workflow 中,因為merge block 只支持兩個input,所以只能使用串行執行,如果只用兩個api 的話就可以merge ,稍快,但對於每個小時發送一次的天氣來說,這點時間差異其實沒有必要,串行就可以了。

    workflow 大概這個樣子。

    其中呢,code block 是把這三個api 的結果梳理一下,送給pushover。

    const obs = $('CWB Realtime Banqiao District').first().json.records.Station[0];
    const we = obs.WeatherElement;
    const fc = $('CWB Forecast Banqiao District').first().json.records.Locations[0].Location[0];
    const uvStation = $('CWA UV Index').first().json.records.Station[0].WeatherElement;
    
    const el = {};
    for (const e of fc.WeatherElement) el[e.ElementName] = e.Time;
    
    const at = el['體感溫度'];
    const pop = el['3小時降雨機率'];
    const wx = el['天氣現象'];
    const rh = el['相對濕度'];
    const ci = el['舒適度指數'];
    
    const days = {};
    for (const t of at) {
      const d = t.DataTime.slice(0, 10);
      if (!days[d]) days[d] = {};
      const h = t.DataTime.slice(11, 13);
      if (h === '06') days[d].morn = t.ElementValue[0].ApparentTemperature;
      if (h === '12') days[d].day = t.ElementValue[0].ApparentTemperature;
      if (h === '21') days[d].night = t.ElementValue[0].ApparentTemperature;
    }
    for (const t of wx) {
      const d = t.StartTime.slice(0, 10);
      if (!days[d]) days[d] = {};
      if (t.StartTime.slice(11, 13) === '12') days[d].wx = t.ElementValue[0].Weather;
    }
    for (const t of pop) {
      const d = t.StartTime.slice(0, 10);
      if (!days[d]) days[d] = {};
      if (t.StartTime.slice(11, 13) === '12') days[d].pop = t.ElementValue[0].ProbabilityOfPrecipitation;
    }
    for (const t of rh) {
      const d = t.DataTime.slice(0, 10);
      if (!days[d]) days[d] = {};
      if (t.DataTime.slice(11, 13) === '12') days[d].rh = t.ElementValue[0].RelativeHumidity;
    }
    for (const t of ci) {
      const d = t.DataTime.slice(0, 10);
      if (!days[d]) days[d] = {};
      if (t.DataTime.slice(11, 13) === '12') days[d].comfort = t.ElementValue[0].ComfortIndexDescription;
    }
    
    const dayTemp = {};
    for (const t of at) {
      const d = t.DataTime.slice(0, 10);
      const v = parseFloat(t.ElementValue[0].ApparentTemperature);
      if (!dayTemp[d]) dayTemp[d] = { min: v, max: v };
      if (v < dayTemp[d].min) dayTemp[d].min = v;
      if (v > dayTemp[d].max) dayTemp[d].max = v;
    }
    
    const sorted = Object.keys(days).sort();
    
    return [{json: {
      station: obs.StationName,
      weather: we.Weather !== '-99' ? we.Weather : uvStation.Weather,
      temp: we.AirTemperature,
      humidity: we.RelativeHumidity,
      wind: we.WindSpeed,
      windDir: el['風向'][0].ElementValue[0].WindDirection,
      rain: we.Now.Precipitation,
      pressure: we.AirPressure,
      high: we.DailyExtreme.DailyHigh.TemperatureInfo.AirTemperature,
      low: we.DailyExtreme.DailyLow.TemperatureInfo.AirTemperature,
      pop: pop[0].ElementValue[0].ProbabilityOfPrecipitation,
      comfort: ci[0].ElementValue[0].ComfortIndexDescription,
      feelsLike: at[0].ElementValue[0].ApparentTemperature,
      uv: uvStation.UVIndex,
      d0_morn: days[sorted[0]]?.morn || '-',
      d0_night: days[sorted[0]]?.night || '-',
      d1_wx: days[sorted[1]]?.wx || '-',
      d1_comfort: days[sorted[1]]?.comfort || '-',
      d1_min: dayTemp[sorted[1]]?.min || '-',
      d1_max: dayTemp[sorted[1]]?.max || '-',
      d1_rh: days[sorted[1]]?.rh || '-',
      d1_pop: days[sorted[1]]?.pop || '-',
      d2_wx: days[sorted[2]]?.wx || '-',
      d2_comfort: days[sorted[2]]?.comfort || '-',
      d2_min: dayTemp[sorted[2]]?.min || '-',
      d2_max: dayTemp[sorted[2]]?.max || '-',
      d2_rh: days[sorted[2]]?.rh || '-',
      d2_pop: days[sorted[2]]?.pop || '-',
    }}];

    在pushover 的block 中將這些output 整理成人類看得懂的樣子。