├── README.md └── AVNScamSimulator.py /README.md: -------------------------------------------------------------------------------- 1 | # AVNScamSimulator 2 | Artificial SCAM simulator for SDB testing purposes 3 | 4 | WARNING 5 | This was created for the express purpose of testing the AVN SDB's metadata recording functionality. 6 | You can use it for other things if you want to, but please note that it may not behave as you expect. 7 | -------------------------------------------------------------------------------- /AVNScamSimulator.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | import random 4 | import katpoint 5 | import numpy as np 6 | from katcp import DeviceServer, Sensor, ProtocolFlags 7 | import sys 8 | 9 | class AVNTarget(katpoint.Target): 10 | def is_visible(self): 11 | if self.azel()[0] >= 0.15: # Somewhat arbitrarily, this is about 8.5 degrees 12 | return True 13 | else: 14 | return False 15 | 16 | 17 | class ScamSimulator(DeviceServer): 18 | 19 | VERSION_INFO = ("scam-simulator-version", 1, 0) 20 | BUILD_INFO = ("scam-simulator-build", 0, 1, "") 21 | 22 | # Optionally set the KATCP protocol version and features. Defaults to 23 | # the latest implemented version of KATCP, with all supported optional 24 | # features 25 | PROTOCOL_INFO = ProtocolFlags(4, 0, set([ 26 | ProtocolFlags.MULTI_CLIENT, 27 | #ProtocolFlags.MESSAGE_IDS, 28 | ])) 29 | 30 | def setup_sensors(self): 31 | # Position sensors 32 | self._SCM_request_azim = Sensor.float("SCM.request-azim", "Sky-space requested azimuth position.", "Degrees CW of north") 33 | self.add_sensor(self._SCM_request_azim) 34 | 35 | self._SCM_request_elev = Sensor.float("SCM.request-elev", "Sky-space requested elevation position.", "Degrees CW of north") 36 | self.add_sensor(self._SCM_request_elev) 37 | 38 | self._SCM_desired_azim = Sensor.float("SCM.desired-azim", "Sky-space desired azimuth position.", "Degrees CW of north") 39 | self.add_sensor(self._SCM_desired_azim) 40 | 41 | self._SCM_desired_elev = Sensor.float("SCM.desired-elev", "Sky-space desired elevation position.", "Degrees CW of north") 42 | self.add_sensor(self._SCM_desired_elev) 43 | 44 | self._SCM_actual_azim = Sensor.float("SCM.actual-azim", "Sky-space actual azimuth position.", "Degrees CW of north") 45 | self.add_sensor(self._SCM_actual_azim) 46 | 47 | self._SCM_actual_elev = Sensor.float("SCM.actual-elev", "Sky-space actual elevation position.", "Degrees CW of north") 48 | self.add_sensor(self._SCM_actual_elev) 49 | 50 | # Pointing model 51 | self._SCM_pmodel1 = Sensor.float("SCM.pmodel1", "Pointing model parameter 1") 52 | self.add_sensor(self._SCM_pmodel1) 53 | self._SCM_pmodel2 = Sensor.float("SCM.pmodel2", "Pointing model parameter 2") 54 | self.add_sensor(self._SCM_pmodel2) 55 | self._SCM_pmodel3 = Sensor.float("SCM.pmodel3", "Pointing model parameter 3") 56 | self.add_sensor(self._SCM_pmodel3) 57 | self._SCM_pmodel4 = Sensor.float("SCM.pmodel4", "Pointing model parameter 4") 58 | self.add_sensor(self._SCM_pmodel4) 59 | self._SCM_pmodel5 = Sensor.float("SCM.pmodel5", "Pointing model parameter 5") 60 | self.add_sensor(self._SCM_pmodel5) 61 | self._SCM_pmodel6 = Sensor.float("SCM.pmodel6", "Pointing model parameter 6") 62 | self.add_sensor(self._SCM_pmodel6) 63 | self._SCM_pmodel7 = Sensor.float("SCM.pmodel7", "Pointing model parameter 7") 64 | self.add_sensor(self._SCM_pmodel7) 65 | self._SCM_pmodel8 = Sensor.float("SCM.pmodel8", "Pointing model parameter 8") 66 | self.add_sensor(self._SCM_pmodel8) 67 | self._SCM_pmodel9 = Sensor.float("SCM.pmodel9", "Pointing model parameter 9") 68 | self.add_sensor(self._SCM_pmodel9) 69 | self._SCM_pmodel10 = Sensor.float("SCM.pmodel10", "Pointing model parameter 10") 70 | self.add_sensor(self._SCM_pmodel10) 71 | self._SCM_pmodel11 = Sensor.float("SCM.pmodel11", "Pointing model parameter 11") 72 | self.add_sensor(self._SCM_pmodel11) 73 | self._SCM_pmodel12 = Sensor.float("SCM.pmodel12", "Pointing model parameter 12") 74 | self.add_sensor(self._SCM_pmodel12) 75 | self._SCM_pmodel13 = Sensor.float("SCM.pmodel13", "Pointing model parameter 13") 76 | self.add_sensor(self._SCM_pmodel13) 77 | self._SCM_pmodel14= Sensor.float("SCM.pmodel14", "Pointing model parameter 14") 78 | self.add_sensor(self._SCM_pmodel14) 79 | self._SCM_pmodel15= Sensor.float("SCM.pmodel15", "Pointing model parameter 15") 80 | self.add_sensor(self._SCM_pmodel15) 81 | self._SCM_pmodel16 = Sensor.float("SCM.pmodel16", "Pointing model parameter 16") 82 | self.add_sensor(self._SCM_pmodel16) 83 | self._SCM_pmodel17 = Sensor.float("SCM.pmodel17", "Pointing model parameter 17") 84 | self.add_sensor(self._SCM_pmodel17) 85 | self._SCM_pmodel18 = Sensor.float("SCM.pmodel18", "Pointing model parameter 18") 86 | self.add_sensor(self._SCM_pmodel18) 87 | self._SCM_pmodel19 = Sensor.float("SCM.pmodel19", "Pointing model parameter 19") 88 | self.add_sensor(self._SCM_pmodel19) 89 | self._SCM_pmodel20 = Sensor.float("SCM.pmodel20", "Pointing model parameter 20") 90 | self.add_sensor(self._SCM_pmodel20) 91 | self._SCM_pmodel21 = Sensor.float("SCM.pmodel21", "Pointing model parameter 21") 92 | self.add_sensor(self._SCM_pmodel21) 93 | self._SCM_pmodel22 = Sensor.float("SCM.pmodel22", "Pointing model parameter 22") 94 | self.add_sensor(self._SCM_pmodel22) 95 | self._SCM_pmodel23 = Sensor.float("SCM.pmodel23", "Pointing model parameter 23") 96 | self.add_sensor(self._SCM_pmodel23) 97 | self._SCM_pmodel24 = Sensor.float("SCM.pmodel24", "Pointing model parameter 24") 98 | self.add_sensor(self._SCM_pmodel24) 99 | self._SCM_pmodel25 = Sensor.float("SCM.pmodel25", "Pointing model parameter 25") 100 | self.add_sensor(self._SCM_pmodel25) 101 | self._SCM_pmodel26 = Sensor.float("SCM.pmodel26", "Pointing model parameter 26") 102 | self.add_sensor(self._SCM_pmodel26) 103 | self._SCM_pmodel27 = Sensor.float("SCM.pmodel27", "Pointing model parameter 27") 104 | self.add_sensor(self._SCM_pmodel27) 105 | self._SCM_pmodel28 = Sensor.float("SCM.pmodel28", "Pointing model parameter 28") 106 | self.add_sensor(self._SCM_pmodel28) 107 | self._SCM_pmodel29 = Sensor.float("SCM.pmodel29", "Pointing model parameter 29") 108 | self.add_sensor(self._SCM_pmodel29) 109 | self._SCM_pmodel30 = Sensor.float("SCM.pmodel30", "Pointing model parameter 30") 110 | self.add_sensor(self._SCM_pmodel30) 111 | 112 | # # Target 113 | self._SCM_Target = Sensor.string("SCM.Target", "Target description string in katpoint format") 114 | self.add_sensor(self._SCM_Target) 115 | 116 | # Antenna activity 117 | self._SCM_Antenna_Activity = Sensor.string("SCM.AntennaActivity", "Antenna activity label") 118 | self.add_sensor(self._SCM_Antenna_Activity) 119 | 120 | # RF sensor information 121 | self._SCM_LcpAttenuation = Sensor.float("SCM.LcpAttenuation", "Variable attenuator setting on LCP") 122 | self.add_sensor(self._SCM_LcpAttenuation) 123 | self._SCM_RcpAttenuation = Sensor.float("SCM.RcpAttenuation", "Variable attenuator setting on RCP") 124 | self.add_sensor(self._SCM_RcpAttenuation) 125 | self._RFC_LcpFreqSel = Sensor.boolean("RFC.LcpFreqSel", "LCP Frequency Select Switch") 126 | self.add_sensor(self._RFC_LcpFreqSel) 127 | self._RFC_RcpFreqSel = Sensor.boolean("RFC.RcpFreqSel", "RCP Frequency Select Switch") 128 | self.add_sensor(self._RFC_RcpFreqSel) 129 | self._RFC_IntermediateStage_5GHz = Sensor.float("RFC.IntermediateStage_5GHz", "5 GHz Intermediate Stage LO frequency") 130 | self.add_sensor(self._RFC_IntermediateStage_5GHz) 131 | self._RFC_IntermediateStage_6_7GHz = Sensor.float("RFC.IntermediateStage_6_7GHz", "6.7 GHz Intermediate Stage LO frequency") 132 | self.add_sensor(self._RFC_IntermediateStage_6_7GHz) 133 | self._RFC_FinalStage = Sensor.float("RFC.FinalStage", "Final Stage LO frequency") 134 | self.add_sensor(self._RFC_FinalStage) 135 | 136 | # Noise diode sensor information 137 | self._RFC_NoiseDiode_1 = Sensor.integer("RFC.NoiseDiode_1", "All noise diode data (bitfield)") 138 | self.add_sensor(self._RFC_NoiseDiode_1) 139 | 140 | # EMS information 141 | self._EMS_WindDirection = Sensor.float("EMS.WindDirection", "Wind direction") 142 | self.add_sensor(self._EMS_WindDirection) 143 | self._EMS_WindSpeed = Sensor.float("EMS.WindSpeed", "Wind speed") 144 | self.add_sensor(self._EMS_WindSpeed) 145 | self._EMS_AirTemperature = Sensor.float("EMS.AirTemperature", "Air temperature") 146 | self.add_sensor(self._EMS_AirTemperature) 147 | self._EMS_AbsolutePressure = Sensor.float("EMS.AbsolutePressure", "Air pressure") 148 | self.add_sensor(self._EMS_AbsolutePressure) 149 | self._EMS_RelativeHumidity = Sensor.float("EMS.RelativeHumidity", "Ambient relative humidity") 150 | self.add_sensor(self._EMS_RelativeHumidity) 151 | 152 | self.animation_thread = threading.Thread(target=self.sensor_value_thread_function) 153 | self.animation_thread.start() 154 | 155 | 156 | def sensor_value_thread_function(self): 157 | 158 | antenna_str = "ant1, 5:45:2.48, -0:18:17.92, 116, 32.0, 0 0 0, %s" % ("0 " * 23) 159 | antenna = katpoint.Antenna(antenna_str) 160 | target_str = "name1 | *name 2, radec, 12:34:56.7, -04:34:34.2, (1000.0 2000.0 1.0)" 161 | self._SCM_Target.set_value(target_str) 162 | self._SCM_Antenna_Activity.set_value("idle") 163 | my_target = AVNTarget(target_str, antenna=antenna) 164 | 165 | self._SCM_pmodel1.set_value(random.random()) 166 | self._SCM_pmodel2.set_value(random.random()) 167 | self._SCM_pmodel3.set_value(random.random()) 168 | self._SCM_pmodel4.set_value(random.random()) 169 | self._SCM_pmodel5.set_value(random.random()) 170 | self._SCM_pmodel6.set_value(random.random()) 171 | self._SCM_pmodel7.set_value(random.random()) 172 | self._SCM_pmodel8.set_value(random.random()) 173 | self._SCM_pmodel9.set_value(random.random()) 174 | self._SCM_pmodel10.set_value(random.random()) 175 | self._SCM_pmodel11.set_value(random.random()) 176 | self._SCM_pmodel12.set_value(random.random()) 177 | self._SCM_pmodel13.set_value(random.random()) 178 | self._SCM_pmodel14.set_value(random.random()) 179 | self._SCM_pmodel15.set_value(random.random()) 180 | self._SCM_pmodel16.set_value(random.random()) 181 | self._SCM_pmodel17.set_value(random.random()) 182 | self._SCM_pmodel18.set_value(random.random()) 183 | self._SCM_pmodel19.set_value(random.random()) 184 | self._SCM_pmodel20.set_value(random.random()) 185 | self._SCM_pmodel21.set_value(random.random()) 186 | self._SCM_pmodel22.set_value(random.random()) 187 | self._SCM_pmodel23.set_value(random.random()) 188 | self._SCM_pmodel24.set_value(random.random()) 189 | self._SCM_pmodel25.set_value(random.random()) 190 | self._SCM_pmodel26.set_value(random.random()) 191 | self._SCM_pmodel27.set_value(random.random()) 192 | self._SCM_pmodel28.set_value(random.random()) 193 | self._SCM_pmodel29.set_value(random.random()) 194 | self._SCM_pmodel30.set_value(random.random()) 195 | 196 | # There shouldn't be step-changes in the environment data, so set initial values. 197 | WindDirection = 360*random.random() 198 | WindSpeed = 50*random.random() 199 | AirTemperature = 50*random.random() 200 | AbsolutePressure = 10*random.random() + 1010.0 201 | RelativeHumidity = 100*random.random() 202 | 203 | while True: 204 | target_azel = my_target.azel() 205 | self._SCM_request_azim.set_value(np.degrees(target_azel[0])) 206 | self._SCM_request_elev.set_value(np.degrees(target_azel[1])) 207 | self._SCM_desired_azim.set_value(np.trunc(10*self._SCM_request_azim.value())/10) 208 | self._SCM_desired_elev.set_value(np.trunc(10*self._SCM_request_elev.value())/10) 209 | self._SCM_actual_azim.set_value(self._SCM_desired_azim.value() + random.random()/25) 210 | self._SCM_actual_elev.set_value(self._SCM_desired_elev.value() + random.random()/25) 211 | 212 | if random.random() > 0.5: 213 | coin_toss = int(4*random.random()) 214 | if coin_toss == 0: 215 | self._SCM_Antenna_Activity.set_value("idle") 216 | elif coin_toss == 1: 217 | self._SCM_Antenna_Activity.set_value("slew") 218 | elif coin_toss == 2: 219 | self._SCM_Antenna_Activity.set_value("track") 220 | elif coin_toss == 3: 221 | self._SCM_Antenna_Activity.set_value("scan") 222 | else: 223 | self._SCM_Antenna_Activity.set_value("stop") # Shouldn't happen, but for logical completeness... 224 | 225 | attenuation = float(random.randint(0, 63))/2 226 | self._SCM_LcpAttenuation.set_value(attenuation) 227 | self._SCM_RcpAttenuation.set_value(attenuation) 228 | 229 | # Frequency band doesn't need to be changed as frequently as the other stuff. 230 | if random.random() > 0.925: 231 | freq_sel = bool(random.randint(0, 1)) 232 | self._RFC_LcpFreqSel.set_value(freq_sel) 233 | self._RFC_RcpFreqSel.set_value(freq_sel) 234 | self._RFC_IntermediateStage_5GHz.set_value(50e6*random.random() + 1.5e9) 235 | self._RFC_IntermediateStage_6_7GHz.set_value(50e6*random.random() + 3.2e9) 236 | self._RFC_FinalStage.set_value(50e6*random.random() + 2.85e9) 237 | 238 | # Noise diode info also doesn't need to change every tick. 239 | if random.random() > 0.6: 240 | input_source = random.randint(0, 3) 241 | bit_2 = 0 242 | enable = random.randint(0, 1) 243 | noise_diode_select = random.randint(1, 15) # Not actually sure if this is supposed to be one-hot 244 | pwm_mark = random.randint(0, 63) 245 | freq_sel = random.randint(0, 3) 246 | bitfield = input_source + bit_2*2**2 + enable*2**3 + noise_diode_select*2**4 + pwm_mark*2**8\ 247 | + freq_sel*2**14 248 | self._RFC_NoiseDiode_1.set_value(bitfield) 249 | 250 | # Climate information only needs to change occasionally too 251 | if random.random() > 0.35: 252 | WindDirection += 2*random.random() - 1 253 | self._EMS_WindDirection.set_value(WindDirection) 254 | WindSpeed += random.random() - 0.5 255 | self._EMS_WindSpeed.set_value(WindSpeed) 256 | AirTemperature += 0.5*random.random() - 0.25 257 | self._EMS_AirTemperature.set_value(AirTemperature) 258 | AbsolutePressure += 0.1*random.random() - 0.05 259 | self._EMS_AbsolutePressure.set_value(AbsolutePressure) 260 | RelativeHumidity += 0.1*random.random() - 0.05 261 | self._EMS_RelativeHumidity.set_value(RelativeHumidity) 262 | 263 | print "\n\nSensors as at {}".format(time.time()) 264 | print "============================================" 265 | for element_name in iter(dir(self)): 266 | element = getattr(self, element_name) 267 | #print "\nElement: {}".format(element) 268 | #print "Type: {}".format(type(element)) 269 | #print "Isinstance: {}".format(isinstance(element, Sensor)) 270 | if isinstance(element, Sensor): 271 | print "{} {} {}".format(element._timestamp, element.name, element._value) 272 | sys.stdout.flush() 273 | time.sleep(random.random()*4 + 1) 274 | 275 | 276 | 277 | if __name__ == "__main__": 278 | server = ScamSimulator("", 1235) 279 | server.start() 280 | 281 | server.animation_thread.join() 282 | server.join() 283 | 284 | --------------------------------------------------------------------------------