編集履歴一覧に戻る
masahitechのアイコン画像

masahitech が 2021年05月16日23時43分51秒 に編集

初版

タイトルの変更

+

ペットのトカゲを現地と同じ環境で飼育する日照管理システムの構築

タグの変更

+

obnizIoTコンテスト

+

JavaScript

+

obniz

+

IoT

+

obnizBoard1Y

メイン画像の変更

メイン画像が設定されました

本文の変更

+

私は現在,アフリカ北部の主にマリ共和国に生息する「マリトゲオアガマ」と,アフリカ南部に生息する「コモンフラットロックリザード」というトカゲを飼育しています. ![マリトゲオアガマ (下)とコモンフラットロックリザード (上)](https://camo.elchika.com/606dd42d313442d74de3783ded359f02ef722b5a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646637383763382d643061352d346232662d383166662d3938636337393230373562392f38663430383533312d393231352d343238612d383738662d333262323039656136613133/) 現地ではアフリカの強い日差しを浴びて生活しているトカゲたちです. 太陽光からの強い紫外線を浴びて体内時計を調整しているので,飼育する際は紫外線ライトの設置が必須です. 飼育下では,ライトがつく時間と消える時間を手動で設定できるプログラムタイマーを使って,紫外線を浴びせる時間を制御するのが一般的です. しかし,実際の日の出や日の入りの時間は毎日変わります. プログラムタイマーは一度設定したらその時間にしか作動せず,現地と同じように日の出や日の入りの時間を制御するためには,設定を毎日手動で変える必要があります. これは大変な労力ですし,時には外出中で設定をできない日もあり,現実的ではありません. そこで, **日の出や日の入りの時間をプログラムで計算し,現地の環境にできるだけ近くなるように紫外線ライトを制御しよう!** と考えました. # システム構成 今回実装した,日照管理システムの構成図は以下のとおりです. ![日照管理システム構成図](https://camo.elchika.com/b2ef0c59b6c1e319c9d0304b073d235c2f837d4a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646637383763382d643061352d346232662d383166662d3938636337393230373562392f64363165323837652d316562382d343531362d613239302d666130633065373230613731/) 紫外線ライトはAC100Vのコンセントにつなぎます. 対して,obnizは最大で約DC5Vの出力しかできません. そこで,**obniznのDC5V出力を利用して,AC100Vをオンオフするリレー回路**を組みました. 今回は秋月電子で売られているソリッド・ステート・リレー (SSR)キットを組み立てて,obnizとコンセントの間に挟みました. また,最終目標は紫外線ライトの制御ですが,紫外線ライトは大電流が流れるため,慎重に回路を組む必要があります. これは安全のために,もう少し時間をかけながらゆっくり工作していきたいので,今回はスマホの充電器とスマホを接続し,充電できているかどうかでシステム自体の動作を検証します. # 部品 - obniz Board 1Y - ソリッド・ステート・リレー ([購入先:秋月電子のリンク](https://akizukidenshi.com/catalog/g/gK-00203/)) - ガラス管ヒューズ,ヒューズボックス - ACコンセント - ACアウトレット - コンセントに電気が来ていることが確認できるもの (今回はスマホとその充電器) # プログラム ソースコードの全体像はとても長いので,記事の最後に貼りました. 上記と同じシステム構成を構築し,プログラムを実行すれば,実現できます. 以下,プログラムの内容を解説します. ## ウェブ実行画面 ![ウェブ実行画面](https://camo.elchika.com/e536c01148976f6688e2633bdb17f49787c61e37/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646637383763382d643061352d346232662d383166662d3938636337393230373562392f61363564616436652d396134352d343364352d383066662d613235343266616637636564/) プログラムの実行後は,ウェブ上の実行画面で次の情報が確認できます.表示自体はhtmlによって行われていて,表示する内容は,後に記述するjavascriptによる日の出と日の入りの計算 (以下,日照計算)プログラムで決定しています. 1. Time Now Japan :日本の現在時刻 Mali :マリ共和国の現在時刻 2. Sunlight Sunrise :マリ共和国の日の出時刻 Sunset :マリ共和国の日の入り時刻 3. Sunset Now. or Sunrise Now. Sunset Now. :太陽が出ていない状態.(夜) Sunrise Now. :太陽が出ている状態.(昼) 4. Daytime Today ? [h] ? [m] ? [s] :今日の太陽が出ている時間の長さを表示 今回は,マリトゲオアガマの原産国であるマリ共和国の時間に合わせて日照計算をしています ([外務省,マリ共和国](https://www.mofa.go.jp/mofaj/area/mali/data.html)). 次に,javascriptで記述された部分の説明をします. ## javascriptによる表示内容の計算 ### 現在時刻 日本とマリ共和国の現在時刻は,javascriptのDateオブジェクトを使って出力しています.ちなみに,マリ共和国のタイムゾーンはUTC+0なので計算が楽で助かりました. ### 日照計算 日照計算には,以下ソースコード内の自作クラスSunlightと,Timeを使用しています. #### Sunlight 日の出時刻と日の入り時刻の計算を担当するクラスです. 太陽がいつどこから昇るかは,Sunrise equationを解いて決定できます. Sunrise equationは,以下の3つを用いた天文学的な計算を行います ([Wikipedia, Sunrise equation](https://en.wikipedia.org/wiki/Sunrise_equation#Calculate_current_Julian_day)). 1. ユリウス通日 2. 緯度 3. 経度 この中で見慣れないのは,ユリウス通日だと思います. > ユリウス通日(ユリウスつうじつ、Julian Day、JD)とは、ユリウス暦[注釈 1]紀元前4713年1月1日、すなわち西暦 -4712年1月1日の正午(世界時)からの日数である ([Wikipedia, ユリウス通日](https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%AA%E3%82%A6%E3%82%B9%E9%80%9A%E6%97%A5)) ユリウス通日は,年月日時分秒を,小数点も含めた日の単位で表すことができます.そのため,日数などを計算するのに便利で天文学などでよく使われているそうです. Sunlight クラスはSunrise equationを実装したものです.上の3つからユリウス通日で表された日の出と日の入り時刻を計算して返します. ただし,ユリウス通日のままでは私たちにはわかりにくいので,一般に使っているグレゴリオ暦に変換する必要があります. このグレゴリオ暦→ユリウス通日変換とユリウス通日→グレゴリオ暦を行うのがTimeクラスです. #### Time [Astro Commons, グレゴリオ暦からユリウス通日を求める](http://astronomy.webcrow.jp/time/gregoriancalendar-julianday.html)を参考にして実装を行いました. ユリウス通日は正午を起点にした計算方法を採用しているので,適宜0.5日の修正を行いながら計算しました. 日の出と日の入りのユリウス通日→グレゴリオ暦の変換は,年月日の計算に床関数を使っている都合で何もない状態からはできないので,現在時刻の年月日も利用して工夫しました. ### Sunset or Sunrise Now 主にソースコードの最後,クラス外での処理です. 現在ユリウス通日が日の出ユリウス通日の前なら,Sunset. 現在ユリウス通日が日の出ユリウス通日の後で日の入りユリウス通日の前なら,Sunrise. 現在ユリウス通日が日の入りユリウス通日の後なら,Sunsetと表示します. ### Daytime Today 今日のマリ共和国でのSunriseとSunsetの間の時間を計算して表しています. 計算は,Time関数内のcalcDayTimeで行っていて,日の出ユリウス通日と日の入りユリウス通日の差分をとって,グレゴリオ暦に直し,今日マリでの昼の長さを教えてくれます. # デモ動画 デモは安全のために,スマホとその充電器で行いました. 紫外線ライトも100Vではあるのですが,ワット数が非常に大きいのでよく調べて回路に間違いがないか確認してから試そうと思っています.(おそらく今回のリレー回路では定格電流が足りないのでは?と思っている) @[twitter](https://twitter.com/MasahitoKumagai/status/1393937954247102464?s=20) # ソースコードの全体像 ```html:日照管理システムのプログラム <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.14.0/obniz.js" crossorigin="anonymous" ></script> </head> <body> <div id="obniz-debug"></div> <div class="time-now"> <h3 class="text-center" style="color:red">Time Now</h3> <div> <h4 class="text-center"> Japan : <strong id="current-jst"></strong><br> Mali : <strong id="current-mst"></strong><br><br> </h4> </div> </div> <div class="sunlight-today"> <h3 class="text-center" style="color:red">Sunlight</h3> <div> <h4 class="text-center"> Sunrise : <strong id="sunrise"></strong><br> Sunset : <strong id="sunset"></strong><br><br> <i><u><h3 id="sunlight" class="text-center" style="color:red"></h3></u></i><br> </h4> </div> </div> <div class="day-time-today"> <h3 class="text-center" style="color:red">Daytime Today</h3> <div> <h4 class="text-center"> <strong id="day-hour"></strong> [h] <strong id="day-minute"></strong> [m] <strong id="day-second"></strong> [s] <br> </h4> </div> </div> <script> class Sunlight { constructor(julianDate, longitude, latitude) { this.julianDate = julianDate; this.longitude = longitude; this.latitude = latitude; } // calculater functions calcCurrentJulianDay() { this.currentJulianDay = this.julianDate - 2451545.0 + 0.0008; } calcMeanSolarTime() { this.meanSolarTime = this.currentJulianDay - this.longitude / 360; } calcSolarMeanAnomaly() { this.solarMeanAnomaly = (357.5291 + 0.98560028*this.meanSolarTime) % 360; } calcEquationOfTheCenter() { this.equationOfTheCenter = 1.9148 * Math.sin(this.degreeToRadian(this.solarMeanAnomaly)) + 0.0200 * Math.sin(this.degreeToRadian(2 * this.solarMeanAnomaly)) + 0.0003 * Math.sin(this.degreeToRadian(3 * this.solarMeanAnomaly)); } calcEclipticLongitude() { this.eclipticLongitude = (this.solarMeanAnomaly + this.equationOfTheCenter + 180 + 102.9372) % 360; } calcSolarTransit() { this.solarTransit = 2451545.0 + this.meanSolarTime + 0.0053 * Math.sin(this.degreeToRadian(this.solarMeanAnomaly)) - 0.0069 * Math.sin(this.degreeToRadian(2 * this.eclipticLongitude)); } calcDeclinationOfTheSun() { let sinDeclinationOfTheSun = Math.sin(this.degreeToRadian(this.eclipticLongitude))*Math.sin(this.degreeToRadian(23.44)); this.declinationOfTheSun = this.radianToDegree(Math.asin(sinDeclinationOfTheSun)); } calcHourAngle() { let numerator = Math.sin(this.degreeToRadian(- 0.83)) - Math.sin(this.degreeToRadian(this.latitude))*Math.sin(this.degreeToRadian(this.declinationOfTheSun)); let denominator = Math.cos(this.degreeToRadian(this.latitude))*Math.cos(this.degreeToRadian(this.declinationOfTheSun)); let cosHourAngle = numerator / denominator; this.hourAngle = this.radianToDegree(Math.acos(cosHourAngle)); } calcSunrise() { this.julianDateOfSunrise = this.solarTransit - this.hourAngle / 360 + 0.5; } calcSunset() { this.julianDateOfSunset = this.solarTransit + this.hourAngle / 360 + 0.5; } run() { this.calcCurrentJulianDay(); this.calcMeanSolarTime(); this.calcSolarMeanAnomaly(); this.calcEquationOfTheCenter(); this.calcEclipticLongitude(); this.calcSolarTransit(); this.calcDeclinationOfTheSun(); this.calcHourAngle(); this.calcSunrise(); this.calcSunset(); } // utility functions degreeToRadian(degree) { return degree * (Math.PI / 180); } radianToDegree(radian) { return radian / (Math.PI / 180); } } class Time { constructor () { this.update(); } update() { this.date = new Date(); console.log(this.date) this.setDate(); } specify(dateSpecify) { this.date = dateSpecify; console.log(this.date) this.setDate(); } setDate() { this.year = this.date.getUTCFullYear(); this.month = this.date.getUTCMonth() + 1; this.day = this.date.getUTCDate(); this.hour = this.date.getUTCHours(); this.minute = this.date.getUTCMinutes(); this.second = this.date.getUTCSeconds(); this.fixYearAndMonth(); this.calcJulianDate(); } fixYearAndMonth() { if (this.month == 1 || this.month == 2) { this.month += 12; this.year -= 1; } } calcJulianDate() { return Math.floor(365.25 * this.year) + Math.floor(this.year / 400) - Math.floor(this.year / 100) + Math.floor(30.59 * (this.month - 2)) + this.day + 1721088.5; //+ this.hour / 24 //+ this.minute / 1440 //+ this.second / 86400 } calcJulianDateNow() { return Math.floor(365.25 * this.year) + Math.floor(this.year / 400) - Math.floor(this.year / 100) + Math.floor(30.59 * (this.month - 2)) + this.day + 1721088.5 + this.hour / 24 + this.minute / 1440 + this.second / 86400; } calcGregorianDate(julian) { let dateJul = new Date(this.date.getTime()); let diffDate = julian - this.julianDate; console.log(diffDate); console.log(this.julianDate); console.log(julian); let diffHour = Math.floor(24 * diffDate); dateJul.setUTCHours(diffHour); console.log(diffHour); let diffMinute = Math.floor(60 * (24 * diffDate - diffHour)); dateJul.setUTCMinutes(diffMinute); console.log(diffMinute); let diffSecond = Math.floor(60 * (24 * 60 * diffDate - 60 * diffHour - diffMinute)); dateJul.setUTCSeconds(diffSecond); console.log(diffSecond); return dateJul; } calcDayTime(sunriseTime, sunsetTime) { let diffDayTime = sunsetTime - sunriseTime; let diffDayHour = Math.floor(24 * diffDayTime); console.log(diffDayHour); let diffDayMinute = Math.floor(60 * (24 * diffDayTime - diffDayHour)); console.log(diffDayMinute); let diffDaySecond = Math.floor(60 * (24 * 60 * diffDayTime - 60 * diffDayHour - diffDayMinute)); console.log(diffDaySecond); return [diffDayHour, diffDayMinute, diffDaySecond]; } get julianDate() { return this.calcJulianDate(); } get julianDateNow() { return this.calcJulianDateNow(); } } var obniz = new Obniz("3569-2712"); obniz.onconnect = async function() { obniz.display.clear(); //let countUp = 0 //検証用 const manageSunLight = () => { let timeNow = new Time(); //timeNow.specify(new Date(2021, 4, 16, 13, 49, countUp++)); //検証用 $("#current-jst").text(timeNow.date.toString()); $("#current-mst").text(timeNow.date.toUTCString()); // Republic of Mali, 17.5706, -3.9961 let sunLight = new Sunlight(timeNow.julianDate, 17.5706, - 3.9961); sunLight.run(); let diffDayTime = timeNow.calcDayTime(sunLight.julianDateOfSunrise, sunLight.julianDateOfSunset) $("#day-hour").text(diffDayTime[0]); $("#day-minute").text(diffDayTime[1]); $("#day-second").text(diffDayTime[2]); $("#sunrise").text(timeNow.calcGregorianDate(sunLight.julianDateOfSunrise).toUTCString()); $("#sunset").text(timeNow.calcGregorianDate(sunLight.julianDateOfSunset).toUTCString()); obniz.display.clear(); console.log(timeNow.julianDateNow) console.log(sunLight.julianDateOfSunrise) console.log(sunLight.julianDateOfSunset) if (timeNow.julianDateNow < sunLight.julianDateOfSunrise) { obniz.display.print("Sunset Now."); $("#sunlight").text("Sunset Now."); obniz.io1.output(false); obniz.io0.output(false); }else if (timeNow.julianDateNow < sunLight.julianDateOfSunset) { obniz.display.print("Sunrise Now."); $("#sunlight").text("Sunrise Now."); obniz.io1.output(false); obniz.io0.output(true); }else { obniz.display.print("Sunset Now."); $("#sunlight").text("Sunset Now."); obniz.io1.output(false); obniz.io0.output(false); } } setInterval(manageSunLight, 1000) }; </script> </body> </html> ```